Skip to content

Commit a4d1a0c

Browse files
Refactor: Extract shared utility functions and improve JSDoc access
Co-authored-by: johnsoncodehk <[email protected]>
1 parent d60336e commit a4d1a0c

File tree

2 files changed

+93
-98
lines changed

2 files changed

+93
-98
lines changed

packages/component-meta/README.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,33 @@ const meta = checker.getComponentMeta(componentPath);
4242

4343
This meta contains really useful stuff like component props, slots, events and more. You can refer to its [type definition](https://github.com/vuejs/language-tools/blob/master/packages/component-meta/lib/types.ts) for more details.
4444

45+
### Extracting component name and description
46+
47+
The component meta also includes `name` and `description` fields at the root level:
48+
49+
- **`name`**: Extracted from the `name` property in the component options (for Options API components)
50+
- **`description`**: Extracted from JSDoc comments above the component export (for TypeScript/JavaScript files)
51+
52+
```ts
53+
/**
54+
* My awesome component description
55+
*/
56+
export default defineComponent({
57+
name: 'MyComponent',
58+
// ... component definition
59+
})
60+
```
61+
62+
When you extract the component meta, you'll get:
63+
```ts
64+
meta.name // 'MyComponent'
65+
meta.description // 'My awesome component description'
66+
```
67+
68+
> **Note**
69+
>
70+
> JSDoc comments on `export default` statements in Vue SFCs (`.vue` files) are not currently supported due to limitations in how TypeScript processes the virtual files generated from SFCs.
71+
4572
### Extracting prop meta
4673

4774
`vue-component-meta` will automatically extract the prop details like its name, default value, is required or not, etc. Additionally, you can even write prop description in source code via [JSDoc](https://jsdoc.app/) comment for that prop.

packages/component-meta/lib/base.ts

Lines changed: 66 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,63 @@ export * from './types';
1818

1919
const windowsPathReg = /\\/g;
2020

21+
// Utility function to get the component node from an AST
22+
function getComponentNodeFromAst(
23+
ast: ts.SourceFile,
24+
exportName: string,
25+
ts: typeof import('typescript'),
26+
): ts.Node | undefined {
27+
let result: ts.Node | undefined;
28+
29+
if (exportName === 'default') {
30+
ast.forEachChild(child => {
31+
if (ts.isExportAssignment(child)) {
32+
result = child.expression;
33+
}
34+
});
35+
}
36+
else {
37+
ast.forEachChild(child => {
38+
if (
39+
ts.isVariableStatement(child)
40+
&& child.modifiers?.some(mod => mod.kind === ts.SyntaxKind.ExportKeyword)
41+
) {
42+
for (const dec of child.declarationList.declarations) {
43+
if (dec.name.getText(ast) === exportName) {
44+
result = dec.initializer;
45+
}
46+
}
47+
}
48+
});
49+
}
50+
51+
return result;
52+
}
53+
54+
// Utility function to get the component options node from a component node
55+
function getComponentOptionsNodeFromComponent(
56+
component: ts.Node | undefined,
57+
ts: typeof import('typescript'),
58+
): ts.ObjectLiteralExpression | undefined {
59+
if (component) {
60+
// export default { ... }
61+
if (ts.isObjectLiteralExpression(component)) {
62+
return component;
63+
}
64+
// export default defineComponent({ ... })
65+
else if (ts.isCallExpression(component)) {
66+
if (component.arguments.length) {
67+
const arg = component.arguments[0]!;
68+
if (ts.isObjectLiteralExpression(arg)) {
69+
return arg;
70+
}
71+
}
72+
}
73+
}
74+
75+
return undefined;
76+
}
77+
2178
export function createCheckerByJsonConfigBase(
2279
ts: typeof import('typescript'),
2380
rootDir: string,
@@ -934,51 +991,12 @@ function readTsComponentDefaultProps(
934991
return {};
935992

936993
function getComponentNode() {
937-
let result: ts.Node | undefined;
938-
939-
if (exportName === 'default') {
940-
ast.forEachChild(child => {
941-
if (ts.isExportAssignment(child)) {
942-
result = child.expression;
943-
}
944-
});
945-
}
946-
else {
947-
ast.forEachChild(child => {
948-
if (
949-
ts.isVariableStatement(child)
950-
&& child.modifiers?.some(mod => mod.kind === ts.SyntaxKind.ExportKeyword)
951-
) {
952-
for (const dec of child.declarationList.declarations) {
953-
if (dec.name.getText(ast) === exportName) {
954-
result = dec.initializer;
955-
}
956-
}
957-
}
958-
});
959-
}
960-
961-
return result;
994+
return getComponentNodeFromAst(ast, exportName, ts);
962995
}
963996

964997
function getComponentOptionsNode() {
965998
const component = getComponentNode();
966-
967-
if (component) {
968-
// export default { ... }
969-
if (ts.isObjectLiteralExpression(component)) {
970-
return component;
971-
}
972-
// export default defineComponent({ ... })
973-
else if (ts.isCallExpression(component)) {
974-
if (component.arguments.length) {
975-
const arg = component.arguments[0]!;
976-
if (ts.isObjectLiteralExpression(arg)) {
977-
return arg;
978-
}
979-
}
980-
}
981-
}
999+
return getComponentOptionsNodeFromComponent(component, ts);
9821000
}
9831001

9841002
function getPropsNode() {
@@ -1074,7 +1092,8 @@ function readComponentName(
10741092
exportName: string,
10751093
ts: typeof import('typescript'),
10761094
): string | undefined {
1077-
const optionsNode = getComponentOptionsNode();
1095+
const componentNode = getComponentNodeFromAst(ast, exportName, ts);
1096+
const optionsNode = getComponentOptionsNodeFromComponent(componentNode, ts);
10781097

10791098
if (optionsNode) {
10801099
const nameProp = optionsNode.properties.find(
@@ -1087,56 +1106,6 @@ function readComponentName(
10871106
}
10881107

10891108
return undefined;
1090-
1091-
function getComponentNode() {
1092-
let result: ts.Node | undefined;
1093-
1094-
if (exportName === 'default') {
1095-
ast.forEachChild(child => {
1096-
if (ts.isExportAssignment(child)) {
1097-
result = child.expression;
1098-
}
1099-
});
1100-
}
1101-
else {
1102-
ast.forEachChild(child => {
1103-
if (
1104-
ts.isVariableStatement(child)
1105-
&& child.modifiers?.some(mod => mod.kind === ts.SyntaxKind.ExportKeyword)
1106-
) {
1107-
for (const dec of child.declarationList.declarations) {
1108-
if (dec.name.getText(ast) === exportName) {
1109-
result = dec.initializer;
1110-
}
1111-
}
1112-
}
1113-
});
1114-
}
1115-
1116-
return result;
1117-
}
1118-
1119-
function getComponentOptionsNode() {
1120-
const component = getComponentNode();
1121-
1122-
if (component) {
1123-
// export default { ... }
1124-
if (ts.isObjectLiteralExpression(component)) {
1125-
return component;
1126-
}
1127-
// export default defineComponent({ ... })
1128-
else if (ts.isCallExpression(component)) {
1129-
if (component.arguments.length) {
1130-
const arg = component.arguments[0]!;
1131-
if (ts.isObjectLiteralExpression(arg)) {
1132-
return arg;
1133-
}
1134-
}
1135-
}
1136-
}
1137-
1138-
return undefined;
1139-
}
11401109
}
11411110

11421111
function readComponentDescription(
@@ -1148,17 +1117,16 @@ function readComponentDescription(
11481117
const exportNode = getExportNode();
11491118

11501119
if (exportNode) {
1151-
// Try to get JSDoc comments from the node
1152-
const jsDocTags = (exportNode as any).jsDoc;
1153-
if (jsDocTags && jsDocTags.length > 0) {
1154-
const jsDoc = jsDocTags[0];
1155-
if (jsDoc.comment) {
1120+
// Try to get JSDoc comments from the node using TypeScript API
1121+
const jsDocComments = ts.getJSDocCommentsAndTags(exportNode);
1122+
for (const jsDoc of jsDocComments) {
1123+
if (ts.isJSDoc(jsDoc) && jsDoc.comment) {
11561124
// Handle both string and array of comment parts
11571125
if (typeof jsDoc.comment === 'string') {
11581126
return jsDoc.comment;
11591127
}
11601128
else if (Array.isArray(jsDoc.comment)) {
1161-
return jsDoc.comment.map((part: any) => part.text || '').join('');
1129+
return jsDoc.comment.map(part => (part as any).text || '').join('');
11621130
}
11631131
}
11641132
}

0 commit comments

Comments
 (0)