Skip to content

Commit 8bbb4d2

Browse files
authored
feat: render @since on top-level symbols; refine inherited handling (#81)
Renders the `@since` tags for top-level symbols as well (classes, interfaces with their own pages). Hides the `@since` tags for Node-native symbols (`Error` etc.).
1 parent 02a33b3 commit 8bbb4d2

4 files changed

Lines changed: 70 additions & 12 deletions

File tree

src/components/Comment.tsx

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,52 @@ export function hasComment(comment?: JSONOutput.Comment): boolean {
2020
);
2121
}
2222

23+
interface SinceReflection {
24+
comment?: JSONOutput.Comment;
25+
inheritedFrom?: JSONOutput.ReferenceType;
26+
sources?: JSONOutput.SourceReference[];
27+
}
28+
29+
// Native runtime symbols: the TypeScript standard library (`lib.*.d.ts`) and
30+
// Node's built-in type definitions (`@types/node`). These have no meaningful
31+
// `@since` of their own — a `@since` on such a member would be copied noise — so
32+
// we always drop it. Other dependencies under `node_modules` are NOT native:
33+
// their `@since` tags (if any) reflect that package's real release history, so
34+
// inherited members keep them.
35+
const NATIVE_SOURCE = /(?:^|\/)node_modules\/(?:typescript\/lib\/|@types\/node\/)/;
36+
37+
function isNativeReflection(reflection: SinceReflection): boolean {
38+
const fileName = reflection.sources?.[0]?.fileName;
39+
40+
return !!fileName && NATIVE_SOURCE.test(fileName);
41+
}
42+
43+
// Return the content of an `@since` tag explicitly present on the reflection.
44+
//
45+
// TypeDoc copies a base member's doc comment onto the members that inherit it,
46+
// so an inherited symbol naturally carries the `@since` it was given in its
47+
// base — we keep that, mirroring how every other inherited doc behaves
48+
// (including symbols inherited from other packages). The exception is members
49+
// inherited from a native runtime type (e.g. `Error`): those carry no real
50+
// version, so we drop it.
51+
export function getSinceContent(
52+
reflection: SinceReflection | undefined,
53+
): JSONOutput.CommentDisplayPart[] | undefined {
54+
const content = reflection?.comment?.blockTags?.find(
55+
(blockTag) => blockTag.tag === '@since',
56+
)?.content;
57+
58+
if (!content) {
59+
return undefined;
60+
}
61+
62+
if (reflection.inheritedFrom && isNativeReflection(reflection)) {
63+
return undefined;
64+
}
65+
66+
return content;
67+
}
68+
2369
export function displayPartsToMarkdown(parts: JSONOutput.CommentDisplayPart[]): string {
2470
return parts
2571
.map((part) => {
@@ -38,8 +84,7 @@ export function Comment({ comment, root, hideTags = [] }: CommentProps) {
3884
}
3985

4086
// Hide custom tags.
41-
hideTags.push('@reference');
42-
hideTags.push('@since');
87+
hideTags.push('@reference', '@since');
4388

4489
const blockTags =
4590
comment.blockTags?.filter((tag) => {

src/components/MemberDeclaration.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import { useMinimalLayout } from '../hooks/useMinimalLayout';
44
import { useRequiredReflection } from '../hooks/useReflection';
55
import { escapeMdx } from '../utils/helpers';
6-
import { Comment, displayPartsToMarkdown, hasComment } from './Comment';
6+
import { Comment, displayPartsToMarkdown, getSinceContent, hasComment } from './Comment';
77
import { DefaultValue } from './DefaultValue';
88
import { Icon } from './Icon';
99
import { Markdown } from './Markdown';
@@ -22,7 +22,7 @@ export function MemberDeclaration({ id }: MemberDeclarationProps) {
2222
const minimal = useMinimalLayout();
2323
const showTypes = reflection.typeParameters && reflection.typeParameters.length > 0;
2424
const showDeclaration = !minimal && extractDeclarationFromType(reflection.type);
25-
const showSince = reflection.comment?.blockTags?.some((tag) => tag.tag === '@since');
25+
const sinceContent = getSinceContent(reflection);
2626

2727
return (
2828
<>
@@ -64,9 +64,9 @@ export function MemberDeclaration({ id }: MemberDeclarationProps) {
6464
</div>
6565
)}
6666

67-
{showSince && (
67+
{sinceContent && (
6868
<div className="tsd-comment-since">
69-
<Markdown content={displayPartsToMarkdown(reflection.comment?.blockTags?.find((tag) => tag.tag === '@since')?.content)} />
69+
<Markdown content={displayPartsToMarkdown(sinceContent)} />
7070
</div>
7171
)}
7272
</div>

src/components/MemberSignatureBody.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,15 @@ import { usePluginData } from '@docusaurus/useGlobalData';
77
import { useMinimalLayout } from '../hooks/useMinimalLayout';
88
import type { TSDSignatureReflection } from '../types';
99
import { ApiDataContext } from './ApiDataContext';
10-
import { Comment, displayPartsToMarkdown, hasComment } from './Comment';
10+
import { Comment, displayPartsToMarkdown, getSinceContent, hasComment } from './Comment';
1111
import { CommentBadges, isCommentWithModifiers } from './CommentBadges';
1212
import { DefaultValue } from './DefaultValue';
1313
import { Flags } from './Flags';
14+
import { Markdown } from './Markdown';
1415
import { hasSources, MemberSources } from './MemberSources';
1516
import { Parameter } from './Parameter';
1617
import { extractDeclarationFromType, Type } from './Type';
1718
import { TypeParameters } from './TypeParameters';
18-
import { Markdown } from './Markdown';
1919

2020
export function hasSigBody(
2121
sig: TSDSignatureReflection | undefined,
@@ -63,9 +63,9 @@ export function MemberSignatureBody({ hideSources, sig }: MemberSignatureBodyPro
6363
const showTypes = sig.typeParameter && sig.typeParameter.length > 0;
6464
const showParams = !minimal && sig.parameters && sig.parameters.length > 0;
6565
const showReturn = !minimal && sig.type;
66-
const showSince = sig.comment?.blockTags?.some((tag) => tag.tag === '@since');
6766

6867
const { reflections } = useContext(ApiDataContext);
68+
const sinceContent = getSinceContent(sig);
6969
const { isPython } = usePluginData('docusaurus-plugin-typedoc-api') as GlobalData;
7070

7171
if (isPython) {
@@ -218,10 +218,10 @@ export function MemberSignatureBody({ hideSources, sig }: MemberSignatureBodyPro
218218
)}
219219

220220
{
221-
showSince && (
221+
sinceContent && (
222222
<>
223223
<div className="tsd-comment-since">
224-
<Markdown content={displayPartsToMarkdown(sig.comment.blockTags?.find((tag) => tag.tag === '@since')?.content)} />
224+
<Markdown content={displayPartsToMarkdown(sinceContent)} />
225225
</div>
226226
</>
227227
)

src/components/Reflection.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@
33
import { useMemo } from 'react';
44
import type { TSDDeclarationReflection, TSDReflection, TSDSignatureReflection } from '../types';
55
import { createHierarchy } from '../utils/hierarchy';
6-
import { Comment, hasComment } from './Comment';
6+
import { Comment, displayPartsToMarkdown, getSinceContent, hasComment } from './Comment';
77
import { CommentBadges, isCommentWithModifiers } from './CommentBadges';
88
import { Hierarchy } from './Hierarchy';
99
import { Icon } from './Icon';
1010
import { Index } from './Index';
11+
import { Markdown } from './Markdown';
1112
import { Members } from './Members';
1213
import { MemberSignatures } from './MemberSignatures';
1314
import { Parameter } from './Parameter';
@@ -20,12 +21,24 @@ export interface ReflectionProps {
2021

2122
export function Reflection({ reflection }: ReflectionProps) {
2223
const hierarchy = useMemo(() => createHierarchy(reflection), [reflection]);
24+
// Callable top-level symbols (functions) render `@since` on their signatures
25+
// below, so only surface it here for non-callable symbols (classes,
26+
// interfaces, enums, type aliases, ...) where it would otherwise be missing.
27+
const hasOwnSignatures =
28+
'signatures' in reflection && !!reflection.signatures && reflection.signatures.length > 0;
29+
const sinceContent = hasOwnSignatures ? undefined : getSinceContent(reflection);
2330

2431
return (
2532
<>
2633
{isCommentWithModifiers(reflection.comment) && <CommentBadges comment={reflection.comment} />}
2734
{hasComment(reflection.comment) && <Comment root comment={reflection.comment} />}
2835

36+
{sinceContent && (
37+
<div className="tsd-comment-since">
38+
<Markdown content={displayPartsToMarkdown(sinceContent)} />
39+
</div>
40+
)}
41+
2942
{'typeParameter' in reflection &&
3043
reflection.typeParameter &&
3144
reflection.typeParameter.length > 0 &&

0 commit comments

Comments
 (0)