Skip to content

Commit d74d4dd

Browse files
committed
Fix JSDocs issues
1 parent 13b3bc5 commit d74d4dd

File tree

13 files changed

+160
-44
lines changed

13 files changed

+160
-44
lines changed

src/parser.ts

Lines changed: 43 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import * as ts from 'typescript';
22
import * as t from './types';
3+
import { Documentation } from './types/documentation';
34

45
/**
56
* Options that specify how the parser should act
@@ -295,24 +296,28 @@ export function parseFromProgram(
295296
if (node.name === componentName) {
296297
const usedProps: Set<string> = new Set();
297298
// squash props
298-
node.types.forEach((typeNode) => {
299-
usedProps.add(typeNode.name);
299+
node.props.forEach((propNode) => {
300+
usedProps.add(propNode.name);
300301

301-
let { [typeNode.name]: currentTypeNode } = props;
302+
let { [propNode.name]: currentTypeNode } = props;
302303
if (currentTypeNode === undefined) {
303-
currentTypeNode = typeNode;
304-
} else if (currentTypeNode.$$id !== typeNode.$$id) {
304+
currentTypeNode = propNode;
305+
} else if (currentTypeNode.$$id !== propNode.$$id) {
305306
currentTypeNode = t.propNode(
306307
currentTypeNode.name,
307-
currentTypeNode.jsDoc,
308-
t.unionNode([currentTypeNode.propType, typeNode.propType]),
309-
currentTypeNode.optional || typeNode.optional,
310-
new Set(Array.from(currentTypeNode.filenames).concat(Array.from(typeNode.filenames))),
308+
{
309+
description: currentTypeNode.description,
310+
defaultValue: currentTypeNode.defaultValue,
311+
visibility: currentTypeNode.visibility,
312+
},
313+
t.unionNode([currentTypeNode.propType, propNode.propType]),
314+
currentTypeNode.optional || propNode.optional,
315+
new Set(Array.from(currentTypeNode.filenames).concat(Array.from(propNode.filenames))),
311316
undefined,
312317
);
313318
}
314319

315-
props[typeNode.name] = currentTypeNode;
320+
props[propNode.name] = currentTypeNode;
316321
});
317322

318323
usedPropsPerSignature.push(usedProps);
@@ -337,6 +342,7 @@ export function parseFromProgram(
337342
}
338343
return propType;
339344
}),
345+
getDocumentation(symbol),
340346
node.getSourceFile().fileName,
341347
),
342348
);
@@ -356,6 +362,7 @@ export function parseFromProgram(
356362
t.componentNode(
357363
name,
358364
properties.map((x) => checkSymbol(x, new Set([(type as any).id]))),
365+
getDocumentation(checker.getSymbolAtLocation(type.symbol?.valueDeclaration!)),
359366
propsFilename,
360367
),
361368
);
@@ -460,9 +467,9 @@ export function parseFromProgram(
460467
}
461468

462469
{
463-
const typeNode = type as any;
470+
const propNode = type as any;
464471

465-
const symbol = typeNode.aliasSymbol ? typeNode.aliasSymbol : typeNode.symbol;
472+
const symbol = propNode.aliasSymbol ? propNode.aliasSymbol : propNode.symbol;
466473
const typeName = symbol ? checker.getFullyQualifiedName(symbol) : null;
467474
switch (typeName) {
468475
case 'global.JSX.Element':
@@ -526,7 +533,7 @@ export function parseFromProgram(
526533
if (type.isLiteral()) {
527534
return t.literalNode(
528535
type.isStringLiteral() ? `"${type.value}"` : type.value,
529-
getDocumentation(type.symbol),
536+
getDocumentation(type.symbol)?.description,
530537
);
531538
}
532539
return t.literalNode(checker.typeToString(type));
@@ -607,7 +614,7 @@ export function parseFromProgram(
607614
return t.simpleTypeNode('any');
608615
}
609616

610-
function getDocumentation(symbol?: ts.Symbol): string | undefined {
617+
function getDocumentation(symbol?: ts.Symbol): Documentation | undefined {
611618
if (!symbol) {
612619
return undefined;
613620
}
@@ -619,17 +626,17 @@ export function parseFromProgram(
619626
if (comments && comments.length === 1) {
620627
const commentNode = comments[0];
621628
if (ts.isJSDoc(commentNode)) {
622-
if (Array.isArray(commentNode.comment)) {
623-
return commentNode.comment.map((x) => x.text).join('\n');
624-
}
625-
626-
return commentNode.comment as string | undefined;
629+
return {
630+
description: commentNode.comment as string | undefined,
631+
defaultValue: commentNode.tags?.find((t) => t.tagName.text === 'default')?.comment,
632+
visibility: getVisibilityFromJSDoc(commentNode),
633+
};
627634
}
628635
}
629636
}
630637

631638
const comment = ts.displayPartsToString(symbol.getDocumentationComment(checker));
632-
return comment ? comment : undefined;
639+
return comment ? { description: comment } : undefined;
633640
}
634641

635642
function getSymbolFileNames(symbol: ts.Symbol): Set<string> {
@@ -642,3 +649,19 @@ export function parseFromProgram(
642649
function hasFlag(typeFlags: number, flag: number) {
643650
return (typeFlags & flag) === flag;
644651
}
652+
653+
function getVisibilityFromJSDoc(doc: ts.JSDoc): Documentation['visibility'] | undefined {
654+
if (doc.tags?.some((tag) => tag.tagName.text === 'public')) {
655+
return 'public';
656+
}
657+
658+
if (doc.tags?.some((tag) => tag.tagName.text === 'internal')) {
659+
return 'internal';
660+
}
661+
662+
if (doc.tags?.some((tag) => tag.tagName.text === 'private')) {
663+
return 'private';
664+
}
665+
666+
return undefined;
667+
}

src/types/documentation.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export interface Documentation {
2+
description?: string;
3+
defaultValue?: any;
4+
visibility?: 'public' | 'private' | 'internal';
5+
}

src/types/nodes/baseNodes.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,3 @@ import { PropNode } from './prop';
33
export interface Node {
44
type: string;
55
}
6-
7-
export interface DefinitionHolder extends Node {
8-
types: PropNode[];
9-
}

src/types/nodes/component.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,29 @@
1-
import { Node, DefinitionHolder } from './baseNodes';
1+
import { Documentation } from '../documentation';
2+
import { Node } from './baseNodes';
23
import { PropNode } from './prop';
34

45
const typeString = 'ComponentNode';
56

6-
export interface ComponentNode extends DefinitionHolder {
7+
export interface ComponentNode extends Node {
78
name: string;
9+
props: PropNode[];
810
propsFilename?: string;
11+
description?: string;
12+
visibility?: Documentation['visibility'];
913
}
1014

1115
export function componentNode(
1216
name: string,
13-
types: PropNode[],
17+
props: PropNode[],
18+
documentation: Documentation | undefined,
1419
propsFilename: string | undefined,
1520
): ComponentNode {
1621
return {
1722
type: typeString,
1823
name: name,
19-
types: types || [],
24+
props: props || [],
25+
description: documentation?.description,
26+
visibility: documentation?.visibility,
2027
propsFilename,
2128
};
2229
}

src/types/nodes/prop.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1+
import { Documentation } from '../documentation';
12
import { Node } from './baseNodes';
23

34
const typeString = 'PropNode';
45

56
export interface PropNode extends Node {
67
name: string;
7-
jsDoc?: string;
8+
description?: string;
9+
defaultValue?: any;
10+
visibility?: Documentation['visibility'];
811
propType: Node;
912
optional: boolean;
1013
filenames: Set<string>;
@@ -16,7 +19,7 @@ export interface PropNode extends Node {
1619

1720
export function propNode(
1821
name: string,
19-
jsDoc: string | undefined,
22+
documentation: Documentation | undefined,
2023
propType: Node,
2124
optional: boolean,
2225
filenames: Set<string>,
@@ -25,7 +28,9 @@ export function propNode(
2528
return {
2629
type: typeString,
2730
name,
28-
jsDoc,
31+
description: documentation?.description,
32+
defaultValue: documentation?.defaultValue,
33+
visibility: documentation?.visibility,
2934
propType,
3035
optional,
3136
filenames,

src/types/props/interface.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
import { Node, DefinitionHolder } from '../nodes/baseNodes';
1+
import { Node } from '../nodes/baseNodes';
22
import { PropNode } from '../nodes/prop';
33

44
const typeString = 'InterfaceNode';
55

6-
export interface InterfaceNode extends DefinitionHolder {}
6+
export interface InterfaceNode extends Node {
7+
types: PropNode[];
8+
}
79

810
export function interfaceNode(types?: PropNode[]): InterfaceNode {
911
return {

src/types/props/literal.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@ const typeString = 'LiteralNode';
44

55
export interface LiteralNode extends Node {
66
value: unknown;
7-
jsDoc?: string;
7+
description?: string;
88
}
99

10-
export function literalNode(value: unknown, jsDoc?: string): LiteralNode {
10+
export function literalNode(value: unknown, description?: string): LiteralNode {
1111
return {
1212
type: typeString,
1313
value,
14-
jsDoc,
14+
description,
1515
};
1616
}
1717

test/callbacks/output.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
{
55
"type": "ComponentNode",
66
"name": "Component",
7-
"types": [
7+
"props": [
88
{
99
"type": "PropNode",
1010
"name": "onChange",
@@ -66,4 +66,4 @@
6666
]
6767
}
6868
]
69-
}
69+
}

test/jsdocs/input.tsx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import * as React from 'react';
2+
3+
/**
4+
* The test component.
5+
*
6+
* @internal
7+
*/
8+
function Component(props: Props): React.ReactElement {
9+
return <div {...props} />;
10+
}
11+
12+
interface Props {
13+
/**
14+
* The class name.
15+
* Used to make the component more classy.
16+
*/
17+
className?: string;
18+
/**
19+
* A value.
20+
*
21+
* @default 10
22+
*/
23+
someValue?: number;
24+
}

test/jsdocs/output.json

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
{
2+
"type": "ProgramNode",
3+
"body": [
4+
{
5+
"type": "ComponentNode",
6+
"name": "Component",
7+
"props": [
8+
{
9+
"type": "PropNode",
10+
"name": "className",
11+
"description": "The class name.\nUsed to make the component more classy.",
12+
"propType": {
13+
"type": "UnionNode",
14+
"types": [
15+
{
16+
"type": "SimpleTypeNode",
17+
"typeName": "undefined"
18+
},
19+
{
20+
"type": "SimpleTypeNode",
21+
"typeName": "string"
22+
}
23+
]
24+
},
25+
"optional": true,
26+
"filenames": {}
27+
},
28+
{
29+
"type": "PropNode",
30+
"name": "someValue",
31+
"description": "A value.",
32+
"defaultValue": "10",
33+
"propType": {
34+
"type": "UnionNode",
35+
"types": [
36+
{
37+
"type": "SimpleTypeNode",
38+
"typeName": "undefined"
39+
},
40+
{
41+
"type": "SimpleTypeNode",
42+
"typeName": "number"
43+
}
44+
]
45+
},
46+
"optional": true,
47+
"filenames": {}
48+
}
49+
],
50+
"description": "The test component.",
51+
"visibility": "internal"
52+
}
53+
]
54+
}

0 commit comments

Comments
 (0)