Skip to content

Commit 41721f9

Browse files
committed
Add support for @interface
Closes #1519
1 parent e370aab commit 41721f9

File tree

8 files changed

+135
-15
lines changed

8 files changed

+135
-15
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@
3737

3838
### Features
3939

40+
- Added support for `@interface` on type aliases to tell TypeDoc to convert the fully resolved type as an interface, #1519
41+
- Added support for `@prop`/`@property` to specify documentation for a child property of a symbol, intended for use with `@interface`.
4042
- Plugins may now return a `Promise<void>` from their `load` function, #185.
4143
- TypeDoc now supports plugins written with ESM, #1635.
4244
- Added `Renderer.preRenderAsyncJobs` and `Renderer.postRenderAsyncJobs`, which may be used by plugins to perform async processing for rendering, #185.

src/lib/converter/comments/discovery.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,10 @@ const wantedKinds: Record<ReflectionKind, ts.SyntaxKind[]> = {
6060
ts.SyntaxKind.ClassDeclaration,
6161
ts.SyntaxKind.BindingElement,
6262
],
63-
[ReflectionKind.Interface]: [ts.SyntaxKind.InterfaceDeclaration],
63+
[ReflectionKind.Interface]: [
64+
ts.SyntaxKind.InterfaceDeclaration,
65+
ts.SyntaxKind.TypeAliasDeclaration,
66+
],
6467
[ReflectionKind.Constructor]: [ts.SyntaxKind.Constructor],
6568
[ReflectionKind.Property]: variablePropertyKinds,
6669
[ReflectionKind.Method]: [

src/lib/converter/plugins/CommentPlugin.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,10 @@ export class CommentPlugin extends ConverterComponent {
153153
comment.removeModifier("@namespace");
154154
}
155155

156+
if (reflection.kindOf(ReflectionKind.Interface)) {
157+
comment.removeModifier("@interface");
158+
}
159+
156160
if (comment.hasModifier("@private")) {
157161
reflection.setFlag(ReflectionFlag.Private);
158162
if (reflection.kindOf(ReflectionKind.CallSignature)) {
@@ -344,6 +348,7 @@ export class CommentPlugin extends ConverterComponent {
344348
}
345349

346350
mergeSeeTags(reflection.comment);
351+
movePropertyTags(reflection.comment, reflection);
347352
}
348353

349354
if (!(reflection instanceof DeclarationReflection)) {
@@ -582,6 +587,23 @@ function moveNestedParamTags(comment: Comment, parameter: ParameterReflection) {
582587
parameter.type?.visit(visitor);
583588
}
584589

590+
function movePropertyTags(comment: Comment, container: Reflection) {
591+
const propTags = comment.blockTags.filter(
592+
(tag) => tag.tag === "@prop" || tag.tag === "@property"
593+
);
594+
595+
for (const prop of propTags) {
596+
if (!prop.name) continue;
597+
598+
const child = container.getChildByName(prop.name);
599+
if (child) {
600+
child.comment = new Comment(
601+
Comment.cloneDisplayParts(prop.content)
602+
);
603+
}
604+
}
605+
}
606+
585607
function mergeSeeTags(comment: Comment) {
586608
const see = comment.getTags("@see");
587609

src/lib/converter/symbols.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,15 @@ function convertTypeAlias(
323323
assert(declaration);
324324

325325
if (ts.isTypeAliasDeclaration(declaration)) {
326+
if (symbol.getJsDocTags().some((tag) => tag.name === "interface")) {
327+
return convertTypeAliasAsInterface(
328+
context,
329+
symbol,
330+
exportSymbol,
331+
declaration
332+
);
333+
}
334+
326335
const reflection = context.createDeclarationReflection(
327336
ReflectionKind.TypeAlias,
328337
symbol,
@@ -351,6 +360,48 @@ function convertTypeAlias(
351360
}
352361
}
353362

363+
function convertTypeAliasAsInterface(
364+
context: Context,
365+
symbol: ts.Symbol,
366+
exportSymbol: ts.Symbol | undefined,
367+
declaration: ts.TypeAliasDeclaration
368+
) {
369+
const reflection = context.createDeclarationReflection(
370+
ReflectionKind.Interface,
371+
symbol,
372+
exportSymbol
373+
);
374+
context.finalizeDeclarationReflection(reflection);
375+
const rc = context.withScope(reflection);
376+
377+
const type = context.checker.getTypeAtLocation(declaration);
378+
379+
// Interfaces have properties
380+
convertSymbols(rc, type.getProperties());
381+
382+
// And type arguments
383+
if (declaration.typeParameters) {
384+
reflection.typeParameters = declaration.typeParameters.map((param) => {
385+
const declaration = param.symbol?.declarations?.[0];
386+
assert(declaration && ts.isTypeParameterDeclaration(declaration));
387+
return createTypeParamReflection(declaration, rc);
388+
});
389+
}
390+
391+
// And maybe call signatures
392+
context.checker
393+
.getSignaturesOfType(type, ts.SignatureKind.Call)
394+
.forEach((sig) =>
395+
createSignature(rc, ReflectionKind.CallSignature, sig, symbol)
396+
);
397+
398+
// And maybe constructor signatures
399+
convertConstructSignatures(rc, symbol);
400+
401+
// And finally, index signatures
402+
convertIndexSignature(rc, symbol);
403+
}
404+
354405
function convertFunctionOrMethod(
355406
context: Context,
356407
symbol: ts.Symbol,

src/lib/utils/options/tsdoc-defaults.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,5 @@ export const modifierTags = [
5555
"@event",
5656
"@overload",
5757
"@namespace",
58+
"@interface",
5859
] as const;

src/test/behaviorTests.ts

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -743,20 +743,6 @@ export const behaviorTests: {
743743
);
744744
},
745745

746-
seeTags(project) {
747-
const foo = query(project, "foo");
748-
equal(
749-
Comment.combineDisplayParts(foo.comment?.getTag("@see")?.content),
750-
" - Double tag\n - Second tag\n"
751-
);
752-
753-
const bar = query(project, "bar");
754-
equal(
755-
Comment.combineDisplayParts(bar.comment?.getTag("@see")?.content),
756-
"Single tag"
757-
);
758-
},
759-
760746
_searchCategoryBoosts(app) {
761747
app.options.setValue("searchCategoryBoosts", {
762748
Cat0: 0,
@@ -803,4 +789,33 @@ export const behaviorTests: {
803789
);
804790
logger.expectNoOtherMessages();
805791
},
792+
793+
seeTags(project) {
794+
const foo = query(project, "foo");
795+
equal(
796+
Comment.combineDisplayParts(foo.comment?.getTag("@see")?.content),
797+
" - Double tag\n - Second tag\n"
798+
);
799+
800+
const bar = query(project, "bar");
801+
equal(
802+
Comment.combineDisplayParts(bar.comment?.getTag("@see")?.content),
803+
"Single tag"
804+
);
805+
},
806+
807+
typeAliasInterface(project) {
808+
const bar = query(project, "Bar");
809+
equal(bar.kind, ReflectionKind.Interface);
810+
equal(
811+
bar.children?.map((c) => c.name),
812+
["a", "b"]
813+
);
814+
815+
const comments = [bar, bar.children[0], bar.children[1]].map((r) =>
816+
Comment.combineDisplayParts(r.comment?.summary)
817+
);
818+
819+
equal(comments, ["Bar docs", "Bar.a docs", "Foo.b docs"]);
820+
},
806821
};
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/**
2+
* Foo docs
3+
*/
4+
export type Foo = {
5+
/**
6+
* Foo.a docs
7+
*/
8+
a: 123;
9+
/**
10+
* Foo.b docs
11+
*/
12+
b: 456;
13+
};
14+
15+
/**
16+
* Bar docs
17+
* @property a Bar.a docs
18+
* @interface
19+
*/
20+
export type Bar = {
21+
[K in keyof Foo]: string;
22+
};

tsdoc.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,10 @@
8686
{
8787
"tagName": "@namespace",
8888
"syntaxKind": "modifier"
89+
},
90+
{
91+
"tagName": "@interface",
92+
"syntaxKind": "modifier"
8993
}
9094
]
9195
}

0 commit comments

Comments
 (0)