Skip to content

Commit f91f804

Browse files
committed
feat: description and default
1 parent 9e911a2 commit f91f804

17 files changed

Lines changed: 273 additions & 26 deletions

File tree

src/index.ts

Lines changed: 42 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ import {
55
type ParameterDeclaration,
66
Project,
77
type SourceFile,
8-
SyntaxKind,
9-
Type,
8+
type TextRange,
9+
VariableDeclaration,
10+
VariableStatement,
1011
ts,
1112
} from "ts-morph";
1213
import { parseProps } from "./props";
@@ -15,6 +16,7 @@ import type { Documentation } from "./types";
1516
type Component = {
1617
getName: () => string;
1718
getParameters: () => ParameterDeclaration[];
19+
getDescription: () => string | undefined;
1820
};
1921

2022
function getProject() {
@@ -40,37 +42,59 @@ export function parse(code: string): Documentation[] {
4042
const project = getProject();
4143
const sourceFile = project.createSourceFile("input.tsx", code);
4244

43-
return getExportedComponents(sourceFile).map((f) => {
45+
return getExportedComponents(sourceFile).map((c) => {
4446
return {
45-
displayName: f.getName(),
46-
props: parseProps(project, f.getParameters()),
47+
displayName: c.getName(),
48+
props: parseProps(project, c.getParameters()),
49+
description: c.getDescription(),
4750
};
4851
});
4952
}
5053

5154
function getExportedComponents(sourceFile: SourceFile): Component[] {
52-
const exportedFunctions = sourceFile
53-
.getFunctions()
54-
.filter((f) => f.isExported())
55-
.map((f) => wrapWithName(f, f.getName() ?? ""));
55+
const components: Component[] = [];
5656

57-
const exportedVariables = sourceFile
58-
.getVariableDeclarations()
59-
.filter((v) => v.isExported() && Node.isArrowFunction(v.getInitializer()))
60-
.map((v) =>
61-
wrapWithName(v.getInitializer() as ArrowFunction, v.getName()!),
62-
);
57+
for (const [name, value] of sourceFile.getExportedDeclarations().entries()) {
58+
if (Node.isFunctionDeclaration(value[0])) {
59+
components.push(
60+
toComponent(
61+
value[0],
62+
name,
63+
value[0].getJsDocs()[0]?.getDescription().trim(),
64+
),
65+
);
66+
}
6367

64-
return [...exportedFunctions, ...exportedVariables];
68+
if (Node.isVariableDeclaration(value[0])) {
69+
const initializer = value[0].getInitializer();
70+
if (Node.isArrowFunction(initializer)) {
71+
components.push(
72+
toComponent(
73+
initializer,
74+
name,
75+
value[0]
76+
.getVariableStatement()
77+
?.getJsDocs()[0]
78+
?.getDescription()
79+
.trim(),
80+
),
81+
);
82+
}
83+
}
84+
}
85+
86+
return components;
6587
}
6688

67-
function wrapWithName(
89+
function toComponent(
6890
f: ArrowFunction | FunctionDeclaration,
6991
name: string,
92+
description?: string,
7093
): Component {
71-
const wrapped = f as Component;
94+
const wrapped = f as unknown as Component;
7295

7396
wrapped.getName = () => name;
97+
wrapped.getDescription = () => description;
7498

7599
return wrapped;
76100
}

src/props.ts

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
import {
22
type ParameterDeclaration,
33
type Project,
4-
type Symbol as TsSymbol,
54
type Type,
6-
printNode,
75
ts,
86
} from "ts-morph";
97
import type {
@@ -12,7 +10,6 @@ import type {
1210
NullType,
1311
ObjectType,
1412
PropDescriptor,
15-
SimpleType,
1613
TypeDescriptor,
1714
UndefinedType,
1815
UnionType,
@@ -38,6 +35,7 @@ export function parseProps(
3835
const description = prop.compilerSymbol.getDocumentationComment(
3936
project.getTypeChecker().compilerObject,
4037
);
38+
const jsDocTags = prop.getJsDocTags();
4139
const required = !prop.isOptional();
4240

4341
props[name] = {
@@ -48,6 +46,30 @@ export function parseProps(
4846
if (description && ts.displayPartsToString(description).trim() !== "") {
4947
props[name].description = ts.displayPartsToString(description);
5048
}
49+
50+
const descriptionTag =
51+
jsDocTags.find((t) => t.getName() === "description") ??
52+
jsDocTags.find((t) => t.getName() === "desc");
53+
if (
54+
descriptionTag &&
55+
ts.displayPartsToString(descriptionTag.getText()).trim() !== ""
56+
) {
57+
props[name].description = ts.displayPartsToString(
58+
descriptionTag.getText(),
59+
);
60+
}
61+
62+
const defaultValue =
63+
jsDocTags.find((t) => t.getName() === "default") ??
64+
jsDocTags.find((t) => t.getName() === "defaultvalue");
65+
if (
66+
defaultValue &&
67+
ts.displayPartsToString(defaultValue.getText()).trim() !== ""
68+
) {
69+
props[name].defaultValue = parseRawValue(
70+
ts.displayPartsToString(defaultValue.getText()),
71+
);
72+
}
5173
}
5274
}
5375

@@ -133,3 +155,37 @@ function parseType(type: Type<ts.Type>): TypeDescriptor {
133155
raw: type.getText(),
134156
};
135157
}
158+
159+
function parseRawValue(value: string): TypeDescriptor;
160+
function parseRawValue(value: unknown, parse: false): TypeDescriptor;
161+
function parseRawValue(value: unknown | string, parse = true): TypeDescriptor {
162+
const parsed = parse ? JSON.parse(value as string) : value;
163+
164+
if (
165+
typeof parsed === "string" ||
166+
typeof parsed === "number" ||
167+
typeof parsed === "boolean"
168+
) {
169+
return {
170+
name: "literal",
171+
value: parsed,
172+
} as LiteralType;
173+
}
174+
175+
if (typeof parsed === "object" && parsed !== null) {
176+
return {
177+
name: "object",
178+
properties: Object.fromEntries(
179+
Object.entries(parsed).map(([key, value]) => [
180+
key,
181+
parseRawValue(value, false),
182+
]),
183+
),
184+
} as ObjectType;
185+
}
186+
187+
return {
188+
name: "unknown",
189+
raw: !parse ? JSON.stringify(value) : (value as string),
190+
};
191+
}

tests/combined/export-multiple/output.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,23 +8,23 @@
88
"props": {}
99
},
1010
{
11-
"displayName": "MyComp4",
11+
"displayName": "MyComp3",
1212
"props": {}
1313
},
1414
{
15-
"displayName": "MyComp7",
15+
"displayName": "MyComp4",
1616
"props": {}
1717
},
1818
{
19-
"displayName": "MyComp3",
19+
"displayName": "MyComp5",
2020
"props": {}
2121
},
2222
{
23-
"displayName": "MyComp5",
23+
"displayName": "MyComp6",
2424
"props": {}
2525
},
2626
{
27-
"displayName": "MyComp6",
27+
"displayName": "MyComp7",
2828
"props": {}
2929
}
3030
]
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/**
2+
* My component
3+
*/
4+
export const MyComp = () => {
5+
return <></>;
6+
};
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[
2+
{
3+
"displayName": "MyComp",
4+
"props": {},
5+
"description": "My component"
6+
}
7+
]
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/**
2+
* My component
3+
*/
4+
export function MyComp() {
5+
return <></>;
6+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[
2+
{
3+
"displayName": "MyComp",
4+
"props": {},
5+
"description": "My component"
6+
}
7+
]
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
interface Props {
2+
/**
3+
* My prop
4+
*
5+
* @defaultvalue "Hello"
6+
*/
7+
myProp: string;
8+
}
9+
10+
export function MyComp(props: Props) {
11+
return <></>;
12+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
[
2+
{
3+
"displayName": "MyComp",
4+
"props": {
5+
"myProp": {
6+
"type": {
7+
"name": "unknown",
8+
"raw": "string"
9+
},
10+
"required": true,
11+
"description": "My prop",
12+
"defaultValue": {
13+
"name": "literal",
14+
"value": "Hello"
15+
}
16+
}
17+
}
18+
}
19+
]
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
interface Props {
2+
/**
3+
* My prop
4+
*
5+
* @default "Hello"
6+
*/
7+
myProp: string;
8+
}
9+
10+
export function MyComp(props: Props) {
11+
return <></>;
12+
}

0 commit comments

Comments
 (0)