Skip to content

Commit 7b7bf66

Browse files
committed
feat: Support for specifying comments on export declarations
Also soft-deprecates the use of `@packageDocumentation` to mark a comment as a module comment. Use `@module` instead. Resolves #1504.
1 parent 1ff431b commit 7b7bf66

File tree

14 files changed

+241
-73
lines changed

14 files changed

+241
-73
lines changed

examples/basic/src/single-export.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,4 @@ class SingleExportedClass {
4646
}
4747
}
4848

49-
/**
50-
* The export statement.
51-
*/
5249
export = SingleExportedClass;

src/lib/converter/context.ts

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,19 @@ export class Context {
7070
this.convertingTypeNode = true;
7171
}
7272

73+
/**
74+
* This is a horrible hack to avoid breaking backwards compatibility for plugins
75+
* that use EVENT_CREATE_DECLARATION. The comment plugin needs to be able to check
76+
* this to properly get the comment for module re-exports:
77+
* ```ts
78+
* /** We should use this comment */
79+
* export * as Mod from "./mod"
80+
* ```
81+
* Will be removed in 0.21.
82+
* @internal
83+
*/
84+
exportSymbol?: ts.Symbol;
85+
7386
private convertingTypeNode = false;
7487

7588
/**
@@ -169,22 +182,31 @@ export class Context {
169182
createDeclarationReflection(
170183
kind: ReflectionKind,
171184
symbol: ts.Symbol | undefined,
172-
name = getHumanName(symbol?.name ?? "unknown")
185+
exportSymbol: ts.Symbol | undefined,
186+
// We need this because modules don't always have symbols.
187+
nameOverride?: string
173188
) {
189+
const name = getHumanName(
190+
nameOverride ?? exportSymbol?.name ?? symbol?.name ?? "unknown"
191+
);
174192
const reflection = new DeclarationReflection(name, kind, this.scope);
175193
this.addChild(reflection);
176194
if (symbol && this.converter.isExternal(symbol)) {
177195
reflection.setFlag(ReflectionFlag.External);
178196
}
197+
if (exportSymbol) {
198+
this.registerReflection(reflection, exportSymbol);
199+
}
179200
this.registerReflection(reflection, symbol);
180201

202+
this.exportSymbol = exportSymbol;
181203
this.converter.trigger(
182204
ConverterEvents.CREATE_DECLARATION,
183205
this,
184206
reflection,
185-
// FIXME this isn't good enough.
186207
symbol && this.converter.getNodesForSymbol(symbol, kind)[0]
187208
);
209+
this.exportSymbol = undefined;
188210

189211
return reflection;
190212
}

src/lib/converter/converter.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,7 @@ export class Converter extends ChildableComponent<
320320
const reflection = context.createDeclarationReflection(
321321
ReflectionKind.Module,
322322
symbol,
323+
void 0,
323324
entryName
324325
);
325326
moduleContext = context.withScope(reflection);

src/lib/converter/factories/comment.ts

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -119,13 +119,26 @@ export function getRawComment(node: ts.Node): string | undefined {
119119
} else {
120120
node = getRootModuleDeclaration(<ts.ModuleDeclaration>node);
121121
}
122+
} else if (node.kind === ts.SyntaxKind.NamespaceExport) {
123+
node = node.parent;
124+
} else if (node.kind === ts.SyntaxKind.ExportSpecifier) {
125+
node = node.parent.parent;
122126
}
123127

124128
const sourceFile = node.getSourceFile();
125129
const comments = getJSDocCommentRanges(node, sourceFile.text);
126130
if (comments.length) {
127131
let comment: ts.CommentRange;
128-
const explicitPackageComment = comments.find((comment) =>
132+
let explicitPackageComment = comments.find((comment) =>
133+
sourceFile.text
134+
.substring(comment.pos, comment.end)
135+
.includes("@module")
136+
);
137+
138+
// TODO: Deprecate and remove. This is an abuse of the @packageDocumentation tag. See:
139+
// https://github.com/TypeStrong/typedoc/issues/1504#issuecomment-775842609
140+
// Deprecate in 0.21, remove in 0.22
141+
explicitPackageComment ??= comments.find((comment) =>
129142
sourceFile.text
130143
.substring(comment.pos, comment.end)
131144
.includes("@packageDocumentation")
@@ -138,20 +151,33 @@ export function getRawComment(node: ts.Node): string | undefined {
138151
// FUTURE: GH#1083, follow deprecation process to phase this out.
139152
comment = comments[0];
140153
} else {
141-
// Single comment that may be a license comment, bail.
154+
// Single comment that may be a license comment, or no comments, bail.
142155
return;
143156
}
144157
} else {
145158
comment = comments[comments.length - 1];
146159
// If a non-SourceFile node comment has this tag, it should not be attached to the node
147160
// as it documents the whole file by convention.
161+
// TODO: Deprecate and remove. This is an abuse of the @packageDocumentation tag. See:
162+
// https://github.com/TypeStrong/typedoc/issues/1504#issuecomment-775842609
163+
// Deprecate in 0.21, remove in 0.22
148164
if (
149165
sourceFile.text
150166
.substring(comment.pos, comment.end)
151167
.includes("@packageDocumentation")
152168
) {
153169
return;
154170
}
171+
172+
// If a non-SourceFile node comment has this tag, it should not be attached to the node
173+
// as it documents the module.
174+
if (
175+
sourceFile.text
176+
.substring(comment.pos, comment.end)
177+
.includes("@module")
178+
) {
179+
return;
180+
}
155181
}
156182

157183
return sourceFile.text.substring(comment.pos, comment.end);
@@ -164,8 +190,8 @@ export function getRawComment(node: ts.Node): string | undefined {
164190
* Parse the given doc comment string.
165191
*
166192
* @param text The doc comment string that should be parsed.
167-
* @param comment The [[Models.Comment]] instance the parsed results should be stored into.
168-
* @returns A populated [[Models.Comment]] instance.
193+
* @param comment The {@link Comment} instance the parsed results should be stored into.
194+
* @returns A populated {@link Comment} instance.
169195
*/
170196
export function parseComment(
171197
text: string,

src/lib/converter/jsdoc.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,20 +23,20 @@ export function convertJsDocAlias(
2323
context: Context,
2424
symbol: ts.Symbol,
2525
declaration: ts.JSDocTypedefTag | ts.JSDocEnumTag,
26-
nameOverride?: string
26+
exportSymbol?: ts.Symbol
2727
) {
2828
if (
2929
declaration.typeExpression &&
3030
ts.isJSDocTypeLiteral(declaration.typeExpression)
3131
) {
32-
convertJsDocInterface(context, declaration, symbol, nameOverride);
32+
convertJsDocInterface(context, declaration, symbol, exportSymbol);
3333
return;
3434
}
3535

3636
const reflection = context.createDeclarationReflection(
3737
ReflectionKind.TypeAlias,
3838
symbol,
39-
nameOverride
39+
exportSymbol
4040
);
4141

4242
reflection.type = context.converter.convertType(
@@ -54,12 +54,12 @@ export function convertJsDocCallback(
5454
context: Context,
5555
symbol: ts.Symbol,
5656
declaration: ts.JSDocCallbackTag,
57-
nameOverride?: string
57+
exportSymbol?: ts.Symbol
5858
) {
5959
const alias = context.createDeclarationReflection(
6060
ReflectionKind.TypeAlias,
6161
symbol,
62-
nameOverride
62+
exportSymbol
6363
);
6464
const ac = context.withScope(alias);
6565

@@ -71,12 +71,12 @@ function convertJsDocInterface(
7171
context: Context,
7272
declaration: ts.JSDocTypedefTag | ts.JSDocEnumTag,
7373
symbol: ts.Symbol,
74-
nameOverride?: string
74+
exportSymbol?: ts.Symbol
7575
) {
7676
const reflection = context.createDeclarationReflection(
7777
ReflectionKind.Interface,
7878
symbol,
79-
nameOverride
79+
exportSymbol
8080
);
8181
const rc = context.withScope(reflection);
8282

src/lib/converter/plugins/CommentPlugin.ts

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ export class CommentPlugin extends ConverterComponent {
107107
) ||
108108
reflection.kind === ReflectionKind.Project
109109
) {
110+
comment.removeTags("module");
110111
comment.removeTags("packagedocumentation");
111112
}
112113
}
@@ -158,33 +159,40 @@ export class CommentPlugin extends ConverterComponent {
158159
* @param node The node that is currently processed if available.
159160
*/
160161
private onDeclaration(
161-
_context: Context,
162+
context: Context,
162163
reflection: Reflection,
163164
node?: ts.Node
164165
) {
165-
if (!node) {
166-
return;
167-
}
168166
if (reflection.kindOf(ReflectionKind.FunctionOrMethod)) {
169167
return;
170168
}
171-
const rawComment = getRawComment(node);
169+
170+
// Clean this up in 0.21. We should really accept a ts.Symbol so we don't need exportSymbol on Context
171+
const exportNode = context.exportSymbol?.getDeclarations()?.[0];
172+
let rawComment = exportNode && getRawComment(exportNode);
173+
rawComment ??= node && getRawComment(node);
172174
if (!rawComment) {
173175
return;
174176
}
175177

176178
const comment = parseComment(rawComment, reflection.comment);
177-
this.applyModifiers(reflection, comment);
178-
this.removeExcludedTags(comment);
179-
reflection.comment = comment;
180179

181180
if (reflection.kindOf(ReflectionKind.Module)) {
182-
const tag = reflection.comment?.getTag("module");
181+
const tag = comment.getTag("module");
183182
if (tag) {
184-
reflection.name = tag.text.trim();
185-
removeIfPresent(reflection.comment?.tags, tag);
183+
// If no name is specified, this is a flag to mark a comment as a module comment
184+
// and should not result in a reflection rename.
185+
const newName = tag.text.trim();
186+
if (newName.length) {
187+
reflection.name = newName;
188+
}
189+
removeIfPresent(comment.tags, tag);
186190
}
187191
}
192+
193+
this.applyModifiers(reflection, comment);
194+
this.removeExcludedTags(comment);
195+
reflection.comment = comment;
188196
}
189197

190198
/**

0 commit comments

Comments
 (0)