compmark-vue 0.2.4 → 0.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +259 -28
- package/dist/cli.mjs +371 -21
- package/dist/index.d.mts +8 -2
- package/dist/index.mjs +371 -21
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -17,25 +17,64 @@ npx compmark-vue ./src/components/Button.vue
|
|
|
17
17
|
|
|
18
18
|
This parses the component and creates `Button.md` in your current directory.
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
## Features
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
- [Props](#props) — runtime and TypeScript generic syntax
|
|
23
|
+
- [Emits](#emits) — array, TypeScript property, and call signature syntax
|
|
24
|
+
- [Slots](#slots) — `defineSlots` with typed bindings, template `<slot>` fallback
|
|
25
|
+
- [Expose](#expose) — `defineExpose` with JSDoc descriptions
|
|
26
|
+
- [Composables](#composables) — auto-detects `useX()` calls in `<script setup>`
|
|
27
|
+
- [JSDoc tags](#jsdoc-tags) — `@deprecated`, `@since`, `@example`, `@see`, `@default`
|
|
28
|
+
- [`@internal`](#internal-components) — exclude components from output
|
|
29
|
+
- [Options API](#options-api) — `export default { props, emits }` support
|
|
30
|
+
- Empty sections are skipped cleanly — no placeholder noise
|
|
31
|
+
|
|
32
|
+
## Examples
|
|
33
|
+
|
|
34
|
+
### Props
|
|
35
|
+
|
|
36
|
+
Runtime syntax, TypeScript generics, and `withDefaults` are all supported:
|
|
23
37
|
|
|
24
38
|
```vue
|
|
25
|
-
<script setup>
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
39
|
+
<script setup lang="ts">
|
|
40
|
+
const props = withDefaults(
|
|
41
|
+
defineProps<{
|
|
42
|
+
/** The label text */
|
|
43
|
+
label: string;
|
|
44
|
+
/** Visual theme */
|
|
45
|
+
theme?: "filled" | "outline";
|
|
46
|
+
disabled?: boolean;
|
|
47
|
+
}>(),
|
|
48
|
+
{
|
|
49
|
+
theme: "filled",
|
|
50
|
+
disabled: false,
|
|
51
|
+
},
|
|
52
|
+
);
|
|
53
|
+
</script>
|
|
54
|
+
```
|
|
31
55
|
|
|
32
|
-
|
|
56
|
+
Output:
|
|
57
|
+
|
|
58
|
+
```md
|
|
59
|
+
## Props
|
|
60
|
+
|
|
61
|
+
| Name | Type | Required | Default | Description |
|
|
62
|
+
| -------- | --------------------- | -------- | ---------- | -------------- |
|
|
63
|
+
| label | string | Yes | - | The label text |
|
|
64
|
+
| theme | 'filled' \| 'outline' | No | `"filled"` | Visual theme |
|
|
65
|
+
| disabled | boolean | No | `false` | - |
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Runtime object syntax is also supported:
|
|
69
|
+
|
|
70
|
+
```vue
|
|
71
|
+
<script setup>
|
|
72
|
+
defineProps({
|
|
33
73
|
/** Title of the dialog */
|
|
34
74
|
title: {
|
|
35
75
|
type: String,
|
|
36
76
|
required: true,
|
|
37
77
|
},
|
|
38
|
-
/** Whether the dialog is visible */
|
|
39
78
|
visible: {
|
|
40
79
|
type: Boolean,
|
|
41
80
|
default: false,
|
|
@@ -44,24 +83,224 @@ const props = defineProps({
|
|
|
44
83
|
</script>
|
|
45
84
|
```
|
|
46
85
|
|
|
47
|
-
|
|
86
|
+
### Emits
|
|
87
|
+
|
|
88
|
+
TypeScript generic syntax with payloads:
|
|
89
|
+
|
|
90
|
+
```vue
|
|
91
|
+
<script setup lang="ts">
|
|
92
|
+
const emit = defineEmits<{
|
|
93
|
+
/** Emitted on save */
|
|
94
|
+
save: [data: Record<string, unknown>];
|
|
95
|
+
/** Emitted on cancel */
|
|
96
|
+
cancel: [];
|
|
97
|
+
}>();
|
|
98
|
+
</script>
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Output:
|
|
102
|
+
|
|
103
|
+
```md
|
|
104
|
+
## Emits
|
|
105
|
+
|
|
106
|
+
| Name | Payload | Description |
|
|
107
|
+
| ------ | ----------------------------- | ----------------- |
|
|
108
|
+
| save | data: Record<string, unknown> | Emitted on save |
|
|
109
|
+
| cancel | - | Emitted on cancel |
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Call signature syntax is also supported:
|
|
113
|
+
|
|
114
|
+
```vue
|
|
115
|
+
<script setup lang="ts">
|
|
116
|
+
defineEmits<{
|
|
117
|
+
(e: "click", payload: MouseEvent): void;
|
|
118
|
+
(e: "submit"): void;
|
|
119
|
+
}>();
|
|
120
|
+
</script>
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Array syntax works too: `defineEmits(["click", "submit"])`.
|
|
124
|
+
|
|
125
|
+
### Slots
|
|
126
|
+
|
|
127
|
+
`defineSlots` provides typed bindings:
|
|
128
|
+
|
|
129
|
+
```vue
|
|
130
|
+
<script setup lang="ts">
|
|
131
|
+
defineSlots<{
|
|
132
|
+
/** Main content */
|
|
133
|
+
default(props: { msg: string }): any;
|
|
134
|
+
/** Header area */
|
|
135
|
+
header(props: { title: string; count: number }): any;
|
|
136
|
+
}>();
|
|
137
|
+
</script>
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Output:
|
|
48
141
|
|
|
49
142
|
```md
|
|
50
|
-
|
|
143
|
+
## Slots
|
|
144
|
+
|
|
145
|
+
| Name | Bindings | Description |
|
|
146
|
+
| ------- | ---------------------------- | ------------ |
|
|
147
|
+
| default | msg: string | Main content |
|
|
148
|
+
| header | title: string, count: number | Header area |
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
If `defineSlots` is not used, slots are extracted from template `<slot>` elements as a fallback:
|
|
152
|
+
|
|
153
|
+
```vue
|
|
154
|
+
<template>
|
|
155
|
+
<div>
|
|
156
|
+
<slot />
|
|
157
|
+
<slot name="header" :title="title" />
|
|
158
|
+
<slot name="footer" />
|
|
159
|
+
</div>
|
|
160
|
+
</template>
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Expose
|
|
164
|
+
|
|
165
|
+
```vue
|
|
166
|
+
<script setup lang="ts">
|
|
167
|
+
defineExpose({
|
|
168
|
+
/** Focus the component */
|
|
169
|
+
focus,
|
|
170
|
+
/** Reset the component state */
|
|
171
|
+
reset,
|
|
172
|
+
});
|
|
173
|
+
</script>
|
|
174
|
+
```
|
|
51
175
|
|
|
176
|
+
Output:
|
|
177
|
+
|
|
178
|
+
```md
|
|
179
|
+
## Exposed
|
|
180
|
+
|
|
181
|
+
| Name | Type | Description |
|
|
182
|
+
| ----- | ------- | ------------------------- |
|
|
183
|
+
| focus | unknown | Focus the component |
|
|
184
|
+
| reset | unknown | Reset the component state |
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Composables
|
|
188
|
+
|
|
189
|
+
Any `useX()` calls in `<script setup>` are automatically detected:
|
|
190
|
+
|
|
191
|
+
```vue
|
|
192
|
+
<script setup lang="ts">
|
|
193
|
+
import { useRouter } from "vue-router";
|
|
194
|
+
import { useMouse } from "@vueuse/core";
|
|
195
|
+
|
|
196
|
+
const router = useRouter();
|
|
197
|
+
const { x, y } = useMouse();
|
|
198
|
+
</script>
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Output:
|
|
202
|
+
|
|
203
|
+
```md
|
|
204
|
+
## Composables Used
|
|
205
|
+
|
|
206
|
+
- `useRouter`
|
|
207
|
+
- `useMouse`
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### JSDoc Tags
|
|
211
|
+
|
|
212
|
+
Props support `@deprecated`, `@since`, `@example`, and `@see`:
|
|
213
|
+
|
|
214
|
+
```vue
|
|
215
|
+
<script setup lang="ts">
|
|
216
|
+
defineProps<{
|
|
217
|
+
/**
|
|
218
|
+
* The label text
|
|
219
|
+
* @deprecated Use `text` instead
|
|
220
|
+
* @since 1.0.0
|
|
221
|
+
* @example "Hello World"
|
|
222
|
+
* @see https://example.com/docs
|
|
223
|
+
*/
|
|
224
|
+
label: string;
|
|
225
|
+
}>();
|
|
226
|
+
</script>
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Output:
|
|
230
|
+
|
|
231
|
+
````md
|
|
52
232
|
## Props
|
|
53
233
|
|
|
54
|
-
| Name
|
|
55
|
-
|
|
|
56
|
-
|
|
|
57
|
-
|
|
234
|
+
| Name | Type | Required | Default | Description |
|
|
235
|
+
| ----- | ------ | -------- | ------- | ----------------------------------------------------------------------------------------------- |
|
|
236
|
+
| label | string | Yes | - | The label text **Deprecated**: Use `text` instead _(since 1.0.0)_ See: https://example.com/docs |
|
|
237
|
+
|
|
238
|
+
**`label` example:**
|
|
239
|
+
|
|
240
|
+
```
|
|
241
|
+
"Hello World"
|
|
242
|
+
```
|
|
243
|
+
````
|
|
244
|
+
|
|
245
|
+
### Internal Components
|
|
246
|
+
|
|
247
|
+
Mark a component with `@internal` to skip it during generation:
|
|
248
|
+
|
|
249
|
+
```vue
|
|
250
|
+
<script setup lang="ts">
|
|
251
|
+
/**
|
|
252
|
+
* @internal
|
|
253
|
+
*/
|
|
254
|
+
defineProps<{
|
|
255
|
+
value: string;
|
|
256
|
+
}>();
|
|
257
|
+
</script>
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
```sh
|
|
261
|
+
$ compmark InternalHelper.vue
|
|
262
|
+
Skipped InternalHelper.vue (marked @internal)
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
### Options API
|
|
266
|
+
|
|
267
|
+
Components using `export default {}` are supported:
|
|
268
|
+
|
|
269
|
+
```vue
|
|
270
|
+
<script>
|
|
271
|
+
export default {
|
|
272
|
+
props: {
|
|
273
|
+
/** The title text */
|
|
274
|
+
title: {
|
|
275
|
+
type: String,
|
|
276
|
+
required: true,
|
|
277
|
+
},
|
|
278
|
+
count: {
|
|
279
|
+
type: Number,
|
|
280
|
+
default: 10,
|
|
281
|
+
},
|
|
282
|
+
},
|
|
283
|
+
emits: ["click", "update"],
|
|
284
|
+
};
|
|
285
|
+
</script>
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
Output:
|
|
289
|
+
|
|
290
|
+
```md
|
|
291
|
+
## Props
|
|
292
|
+
|
|
293
|
+
| Name | Type | Required | Default | Description |
|
|
294
|
+
| ----- | ------ | -------- | ------- | -------------- |
|
|
295
|
+
| title | String | Yes | - | The title text |
|
|
296
|
+
| count | Number | No | `10` | - |
|
|
58
297
|
|
|
59
298
|
## Emits
|
|
60
299
|
|
|
61
|
-
| Name | Description
|
|
62
|
-
| ------ |
|
|
63
|
-
|
|
|
64
|
-
|
|
|
300
|
+
| Name | Description |
|
|
301
|
+
| ------ | ----------- |
|
|
302
|
+
| click | - |
|
|
303
|
+
| update | - |
|
|
65
304
|
```
|
|
66
305
|
|
|
67
306
|
## Programmatic API
|
|
@@ -86,14 +325,6 @@ const doc = parseSFC(source, "Button.vue");
|
|
|
86
325
|
const md = generateMarkdown(doc);
|
|
87
326
|
```
|
|
88
327
|
|
|
89
|
-
## Supported Syntax
|
|
90
|
-
|
|
91
|
-
- `defineProps({ ... })` — shorthand (`String`), array type (`[String, Number]`), and full object syntax
|
|
92
|
-
- `defineEmits([...])` — array syntax
|
|
93
|
-
- JSDoc comments on props and emits (`/** ... */`)
|
|
94
|
-
- `const props = defineProps(...)` variable assignment pattern
|
|
95
|
-
- Default value extraction (string, number, boolean literals, arrow functions)
|
|
96
|
-
|
|
97
328
|
## Development
|
|
98
329
|
|
|
99
330
|
<details>
|
package/dist/cli.mjs
CHANGED
|
@@ -1,7 +1,270 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
-
import { join, resolve } from "node:path";
|
|
4
|
-
import { compileScript, parse } from "@vue/compiler-sfc";
|
|
2
|
+
import { existsSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { dirname, join, resolve } from "node:path";
|
|
4
|
+
import { babelParse, compileScript, parse } from "@vue/compiler-sfc";
|
|
5
|
+
//#region src/resolver.ts
|
|
6
|
+
function resolveImportPath(importSource, sfcDir) {
|
|
7
|
+
try {
|
|
8
|
+
if (!importSource.startsWith(".") && !importSource.startsWith("@/") && !importSource.startsWith("~/")) return null;
|
|
9
|
+
if (importSource.startsWith("./") || importSource.startsWith("../")) return tryResolveFile(resolve(sfcDir, importSource));
|
|
10
|
+
const tsconfig = findTsConfig(sfcDir);
|
|
11
|
+
if (!tsconfig) return null;
|
|
12
|
+
const { paths, baseUrl } = readTsConfigPaths(tsconfig);
|
|
13
|
+
if (!paths) return null;
|
|
14
|
+
const configDir = dirname(tsconfig);
|
|
15
|
+
const resolvedBaseUrl = baseUrl ? resolve(configDir, baseUrl) : configDir;
|
|
16
|
+
for (const [pattern, targets] of Object.entries(paths)) {
|
|
17
|
+
const prefix = pattern.replace(/\*$/, "");
|
|
18
|
+
if (!importSource.startsWith(prefix)) continue;
|
|
19
|
+
const remainder = importSource.slice(prefix.length);
|
|
20
|
+
for (const target of targets) {
|
|
21
|
+
const result = tryResolveFile(resolve(resolvedBaseUrl, target.replace(/\*$/, "") + remainder));
|
|
22
|
+
if (result) return result;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
} catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function tryResolveFile(basePath) {
|
|
31
|
+
if (existsSync(basePath) && !isDirectory(basePath)) return basePath;
|
|
32
|
+
for (const ext of [".ts", ".js"]) {
|
|
33
|
+
const candidate = basePath + ext;
|
|
34
|
+
if (existsSync(candidate)) return candidate;
|
|
35
|
+
}
|
|
36
|
+
for (const ext of ["/index.ts", "/index.js"]) {
|
|
37
|
+
const candidate = basePath + ext;
|
|
38
|
+
if (existsSync(candidate)) return candidate;
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
function isDirectory(filePath) {
|
|
43
|
+
try {
|
|
44
|
+
return statSync(filePath).isDirectory();
|
|
45
|
+
} catch {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function findTsConfig(startDir) {
|
|
50
|
+
let dir = resolve(startDir);
|
|
51
|
+
const root = resolve("/");
|
|
52
|
+
while (dir !== root) {
|
|
53
|
+
const tsconfig = join(dir, "tsconfig.json");
|
|
54
|
+
if (existsSync(tsconfig)) return tsconfig;
|
|
55
|
+
const jsconfig = join(dir, "jsconfig.json");
|
|
56
|
+
if (existsSync(jsconfig)) return jsconfig;
|
|
57
|
+
const parent = dirname(dir);
|
|
58
|
+
if (parent === dir) break;
|
|
59
|
+
dir = parent;
|
|
60
|
+
}
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
function readTsConfigPaths(configPath) {
|
|
64
|
+
try {
|
|
65
|
+
const content = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
66
|
+
let paths = content.compilerOptions?.paths ?? null;
|
|
67
|
+
let baseUrl = content.compilerOptions?.baseUrl;
|
|
68
|
+
if (content.extends) {
|
|
69
|
+
const parentPath = resolve(dirname(configPath), content.extends);
|
|
70
|
+
const parentConfigFile = parentPath.endsWith(".json") ? parentPath : parentPath + ".json";
|
|
71
|
+
if (existsSync(parentConfigFile)) try {
|
|
72
|
+
const parentContent = JSON.parse(readFileSync(parentConfigFile, "utf-8"));
|
|
73
|
+
const parentPaths = parentContent.compilerOptions?.paths;
|
|
74
|
+
const parentBaseUrl = parentContent.compilerOptions?.baseUrl;
|
|
75
|
+
if (!paths && parentPaths) paths = parentPaths;
|
|
76
|
+
if (!baseUrl && parentBaseUrl) baseUrl = parentBaseUrl;
|
|
77
|
+
} catch {}
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
paths,
|
|
81
|
+
baseUrl
|
|
82
|
+
};
|
|
83
|
+
} catch {
|
|
84
|
+
return {
|
|
85
|
+
paths: null,
|
|
86
|
+
baseUrl: void 0
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function resolveComposableTypes(filePath, exportName, variableNames) {
|
|
91
|
+
try {
|
|
92
|
+
const funcNode = findExportedFunction(babelParse(readFileSync(filePath, "utf-8"), {
|
|
93
|
+
plugins: ["typescript", "jsx"],
|
|
94
|
+
sourceType: "module"
|
|
95
|
+
}).program.body, exportName);
|
|
96
|
+
if (!funcNode) return /* @__PURE__ */ new Map();
|
|
97
|
+
const body = getFunctionBody(funcNode);
|
|
98
|
+
if (!body) return /* @__PURE__ */ new Map();
|
|
99
|
+
const returnProps = findReturnProperties(body);
|
|
100
|
+
if (!returnProps) return /* @__PURE__ */ new Map();
|
|
101
|
+
const result = /* @__PURE__ */ new Map();
|
|
102
|
+
const nameSet = new Set(variableNames);
|
|
103
|
+
for (const prop of returnProps) {
|
|
104
|
+
let propName = null;
|
|
105
|
+
if (prop.type === "ObjectProperty") propName = prop.key.type === "Identifier" ? prop.key.name : prop.key.type === "StringLiteral" ? prop.key.value : null;
|
|
106
|
+
else if (prop.type === "ObjectMethod") {
|
|
107
|
+
propName = prop.key.type === "Identifier" ? prop.key.name : null;
|
|
108
|
+
if (propName && nameSet.has(propName)) result.set(propName, inferFunctionSignature(prop));
|
|
109
|
+
continue;
|
|
110
|
+
} else if (prop.type === "SpreadElement") continue;
|
|
111
|
+
if (!propName || !nameSet.has(propName)) continue;
|
|
112
|
+
if (prop.type === "ObjectProperty" && prop.shorthand) {
|
|
113
|
+
const type = traceVariableType(propName, body);
|
|
114
|
+
result.set(propName, type);
|
|
115
|
+
} else if (prop.type === "ObjectProperty") {
|
|
116
|
+
const type = inferType(prop.value);
|
|
117
|
+
result.set(propName, type);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return result;
|
|
121
|
+
} catch {
|
|
122
|
+
return /* @__PURE__ */ new Map();
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function findExportedFunction(stmts, exportName) {
|
|
126
|
+
for (const stmt of stmts) {
|
|
127
|
+
if (stmt.type === "ExportNamedDeclaration" && stmt.declaration?.type === "FunctionDeclaration" && stmt.declaration.id?.name === exportName) return stmt.declaration;
|
|
128
|
+
if (stmt.type === "ExportNamedDeclaration" && stmt.declaration?.type === "VariableDeclaration") {
|
|
129
|
+
for (const decl of stmt.declaration.declarations) if (decl.id.type === "Identifier" && decl.id.name === exportName && decl.init && (decl.init.type === "ArrowFunctionExpression" || decl.init.type === "FunctionExpression")) return decl.init;
|
|
130
|
+
}
|
|
131
|
+
if (stmt.type === "ExportDefaultDeclaration" && stmt.declaration.type === "FunctionDeclaration" && stmt.declaration.id?.name === exportName) return stmt.declaration;
|
|
132
|
+
if (stmt.type === "ExportDefaultDeclaration" && stmt.declaration.type === "FunctionDeclaration" && !stmt.declaration.id) return stmt.declaration;
|
|
133
|
+
if (stmt.type === "ExportDefaultDeclaration" && (stmt.declaration.type === "ArrowFunctionExpression" || stmt.declaration.type === "FunctionExpression")) return stmt.declaration;
|
|
134
|
+
if (stmt.type === "FunctionDeclaration" && stmt.id?.name === exportName) {
|
|
135
|
+
if (stmts.some((s) => s.type === "ExportNamedDeclaration" && !s.declaration && s.specifiers.some((spec) => spec.type === "ExportSpecifier" && (spec.local.type === "Identifier" && spec.local.name === exportName || spec.exported.type === "Identifier" && spec.exported.name === exportName)))) return stmt;
|
|
136
|
+
}
|
|
137
|
+
if (stmt.type === "VariableDeclaration") {
|
|
138
|
+
for (const decl of stmt.declarations) if (decl.id.type === "Identifier" && decl.id.name === exportName && decl.init && (decl.init.type === "ArrowFunctionExpression" || decl.init.type === "FunctionExpression")) {
|
|
139
|
+
if (stmts.some((s) => s.type === "ExportNamedDeclaration" && !s.declaration && s.specifiers.some((spec) => spec.type === "ExportSpecifier" && (spec.local.type === "Identifier" && spec.local.name === exportName || spec.exported.type === "Identifier" && spec.exported.name === exportName)))) return decl.init;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
function getFunctionBody(node) {
|
|
146
|
+
if (node.body.type === "BlockStatement") return node.body.body;
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
function findReturnProperties(body) {
|
|
150
|
+
for (let i = body.length - 1; i >= 0; i--) {
|
|
151
|
+
const stmt = body[i];
|
|
152
|
+
if (stmt.type === "ReturnStatement" && stmt.argument?.type === "ObjectExpression") return stmt.argument.properties;
|
|
153
|
+
}
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
function traceVariableType(name, body) {
|
|
157
|
+
for (let i = body.length - 1; i >= 0; i--) {
|
|
158
|
+
const stmt = body[i];
|
|
159
|
+
if (stmt.type === "FunctionDeclaration" && stmt.id?.name === name) return inferFunctionSignature(stmt);
|
|
160
|
+
if (stmt.type === "VariableDeclaration") {
|
|
161
|
+
for (const decl of stmt.declarations) if (decl.id.type === "Identifier" && decl.id.name === name && decl.init) return inferType(decl.init);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return "unknown";
|
|
165
|
+
}
|
|
166
|
+
function inferType(node) {
|
|
167
|
+
if (node.type === "CallExpression" && node.callee.type === "Identifier" && node.callee.name === "ref") {
|
|
168
|
+
const typeParams = node.typeParameters;
|
|
169
|
+
if (typeParams?.params?.length > 0) return `Ref<${resolveTypeAnnotation(typeParams.params[0])}>`;
|
|
170
|
+
const arg = node.arguments[0];
|
|
171
|
+
if (!arg) return "Ref<unknown>";
|
|
172
|
+
return `Ref<${inferLiteralType(arg)}>`;
|
|
173
|
+
}
|
|
174
|
+
if (node.type === "CallExpression" && node.callee.type === "Identifier" && node.callee.name === "computed") return "ComputedRef";
|
|
175
|
+
if (node.type === "CallExpression" && node.callee.type === "Identifier" && node.callee.name === "reactive") return "Object";
|
|
176
|
+
if (node.type === "ArrowFunctionExpression" || node.type === "FunctionExpression") return inferFunctionSignature(node);
|
|
177
|
+
if (node.type === "CallExpression" && node.callee.type === "Identifier" && /^use[A-Z]/.test(node.callee.name)) return "unknown";
|
|
178
|
+
return inferLiteralType(node);
|
|
179
|
+
}
|
|
180
|
+
function inferLiteralType(node) {
|
|
181
|
+
switch (node.type) {
|
|
182
|
+
case "NumericLiteral": return "number";
|
|
183
|
+
case "StringLiteral": return "string";
|
|
184
|
+
case "BooleanLiteral": return "boolean";
|
|
185
|
+
case "NullLiteral": return "null";
|
|
186
|
+
case "TemplateLiteral": return "string";
|
|
187
|
+
case "ArrayExpression": return "Array";
|
|
188
|
+
case "ObjectExpression": return "Object";
|
|
189
|
+
default: return "unknown";
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
function inferFunctionSignature(node) {
|
|
193
|
+
return `(${extractParams(node.params ?? [])}) => ${extractReturnType(node)}`;
|
|
194
|
+
}
|
|
195
|
+
function extractParams(params) {
|
|
196
|
+
return params.map((param) => {
|
|
197
|
+
if (param.type === "Identifier") {
|
|
198
|
+
const annotation = param.typeAnnotation?.typeAnnotation;
|
|
199
|
+
if (annotation) return `${param.name}: ${resolveTypeAnnotation(annotation)}`;
|
|
200
|
+
return param.name;
|
|
201
|
+
}
|
|
202
|
+
if (param.type === "AssignmentPattern") {
|
|
203
|
+
const left = param.left;
|
|
204
|
+
if (left.type === "Identifier") {
|
|
205
|
+
const annotation = left.typeAnnotation?.typeAnnotation;
|
|
206
|
+
if (annotation) return `${left.name}: ${resolveTypeAnnotation(annotation)}`;
|
|
207
|
+
return left.name;
|
|
208
|
+
}
|
|
209
|
+
return "arg";
|
|
210
|
+
}
|
|
211
|
+
if (param.type === "RestElement") {
|
|
212
|
+
const arg = param.argument;
|
|
213
|
+
if (arg.type === "Identifier") {
|
|
214
|
+
const annotation = arg.typeAnnotation?.typeAnnotation;
|
|
215
|
+
if (annotation) return `...${arg.name}: ${resolveTypeAnnotation(annotation)}`;
|
|
216
|
+
return `...${arg.name}`;
|
|
217
|
+
}
|
|
218
|
+
return "...args";
|
|
219
|
+
}
|
|
220
|
+
if (param.type === "ObjectPattern") return "options";
|
|
221
|
+
if (param.type === "ArrayPattern") return "args";
|
|
222
|
+
return "arg";
|
|
223
|
+
}).join(", ");
|
|
224
|
+
}
|
|
225
|
+
function extractReturnType(node) {
|
|
226
|
+
const annotation = node.returnType?.typeAnnotation ?? node.typeAnnotation?.typeAnnotation;
|
|
227
|
+
let baseType;
|
|
228
|
+
if (annotation) baseType = resolveTypeAnnotation(annotation);
|
|
229
|
+
else baseType = "void";
|
|
230
|
+
if (node.async && baseType !== "void") return `Promise<${baseType}>`;
|
|
231
|
+
if (node.async) return "Promise<void>";
|
|
232
|
+
return baseType;
|
|
233
|
+
}
|
|
234
|
+
function resolveTypeAnnotation(node) {
|
|
235
|
+
if (!node) return "unknown";
|
|
236
|
+
switch (node.type) {
|
|
237
|
+
case "TSStringKeyword": return "string";
|
|
238
|
+
case "TSNumberKeyword": return "number";
|
|
239
|
+
case "TSBooleanKeyword": return "boolean";
|
|
240
|
+
case "TSVoidKeyword": return "void";
|
|
241
|
+
case "TSAnyKeyword": return "any";
|
|
242
|
+
case "TSNullKeyword": return "null";
|
|
243
|
+
case "TSUndefinedKeyword": return "undefined";
|
|
244
|
+
case "TSObjectKeyword": return "object";
|
|
245
|
+
case "TSNeverKeyword": return "never";
|
|
246
|
+
case "TSUnknownKeyword": return "unknown";
|
|
247
|
+
case "TSTypeReference": {
|
|
248
|
+
const name = node.typeName?.type === "Identifier" ? node.typeName.name : node.typeName?.type === "TSQualifiedName" ? `${node.typeName.left?.name ?? ""}.${node.typeName.right?.name ?? ""}` : "unknown";
|
|
249
|
+
if (node.typeParameters?.params?.length > 0) return `${name}<${node.typeParameters.params.map((p) => resolveTypeAnnotation(p)).join(", ")}>`;
|
|
250
|
+
return name;
|
|
251
|
+
}
|
|
252
|
+
case "TSUnionType": return node.types.map((t) => resolveTypeAnnotation(t)).join(" | ");
|
|
253
|
+
case "TSIntersectionType": return node.types.map((t) => resolveTypeAnnotation(t)).join(" & ");
|
|
254
|
+
case "TSArrayType": return `${resolveTypeAnnotation(node.elementType)}[]`;
|
|
255
|
+
case "TSLiteralType":
|
|
256
|
+
if (node.literal.type === "StringLiteral") return `'${node.literal.value}'`;
|
|
257
|
+
if (node.literal.type === "NumericLiteral") return String(node.literal.value);
|
|
258
|
+
if (node.literal.type === "BooleanLiteral") return String(node.literal.value);
|
|
259
|
+
return "unknown";
|
|
260
|
+
case "TSFunctionType": return "Function";
|
|
261
|
+
case "TSTupleType": return `[${(node.elementTypes ?? []).map((t) => resolveTypeAnnotation(t)).join(", ")}]`;
|
|
262
|
+
case "TSParenthesizedType": return resolveTypeAnnotation(node.typeAnnotation);
|
|
263
|
+
case "TSTypeLiteral": return "object";
|
|
264
|
+
default: return "unknown";
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
//#endregion
|
|
5
268
|
//#region src/parser.ts
|
|
6
269
|
function parseJSDocTags(comments) {
|
|
7
270
|
const result = { description: "" };
|
|
@@ -22,7 +285,7 @@ function parseJSDocTags(comments) {
|
|
|
22
285
|
result.description = descLines.join(" ");
|
|
23
286
|
return result;
|
|
24
287
|
}
|
|
25
|
-
function parseSFC(source, filename) {
|
|
288
|
+
function parseSFC(source, filename, sfcDir) {
|
|
26
289
|
const doc = {
|
|
27
290
|
name: filename.replace(/\.vue$/, "").split("/").pop() ?? "Unknown",
|
|
28
291
|
props: [],
|
|
@@ -50,7 +313,7 @@ function parseSFC(source, filename) {
|
|
|
50
313
|
else if (callee === "defineSlots" && typeParams?.params[0]?.type === "TSTypeLiteral") doc.slots = extractTypeSlots(typeParams.params[0]);
|
|
51
314
|
else if (callee === "defineExpose" && args[0]?.type === "ObjectExpression") doc.exposes = extractExposes(args[0], scriptSource);
|
|
52
315
|
}
|
|
53
|
-
doc.composables = extractComposables(setupAst);
|
|
316
|
+
doc.composables = extractComposables(setupAst, buildImportMap(setupAst), sfcDir);
|
|
54
317
|
}
|
|
55
318
|
const scriptAst = compiled.scriptAst;
|
|
56
319
|
if (scriptAst && doc.props.length === 0 && doc.emits.length === 0) {
|
|
@@ -331,26 +594,94 @@ function extractExposes(obj, _source) {
|
|
|
331
594
|
}
|
|
332
595
|
return exposes;
|
|
333
596
|
}
|
|
334
|
-
function
|
|
597
|
+
function buildImportMap(ast) {
|
|
598
|
+
const map = /* @__PURE__ */ new Map();
|
|
599
|
+
for (const stmt of ast) if (stmt.type === "ImportDeclaration") {
|
|
600
|
+
for (const spec of stmt.specifiers ?? []) if (spec.type === "ImportSpecifier" || spec.type === "ImportDefaultSpecifier") map.set(spec.local.name, stmt.source.value);
|
|
601
|
+
}
|
|
602
|
+
return map;
|
|
603
|
+
}
|
|
604
|
+
function extractVariablesFromPattern(decl) {
|
|
605
|
+
const id = decl.id;
|
|
606
|
+
if (!id) return [];
|
|
607
|
+
if (id.type === "Identifier") {
|
|
608
|
+
const v = { name: id.name };
|
|
609
|
+
if (id.typeAnnotation?.typeAnnotation) v.type = resolveTypeString(id.typeAnnotation.typeAnnotation);
|
|
610
|
+
return [v];
|
|
611
|
+
}
|
|
612
|
+
if (id.type === "ObjectPattern") {
|
|
613
|
+
const vars = [];
|
|
614
|
+
const typeAnnotation = id.typeAnnotation?.typeAnnotation;
|
|
615
|
+
const typeMembers = typeAnnotation?.type === "TSTypeLiteral" ? typeAnnotation.members : null;
|
|
616
|
+
for (const prop of id.properties) if (prop.type === "RestElement") {
|
|
617
|
+
const name = prop.argument?.name ?? "rest";
|
|
618
|
+
vars.push({ name });
|
|
619
|
+
} else if (prop.type === "ObjectProperty") {
|
|
620
|
+
const name = prop.value?.type === "Identifier" ? prop.value.name : prop.value?.type === "AssignmentPattern" && prop.value.left?.type === "Identifier" ? prop.value.left.name : prop.key?.type === "Identifier" ? prop.key.name : "";
|
|
621
|
+
if (!name) continue;
|
|
622
|
+
const v = { name };
|
|
623
|
+
if (typeMembers) {
|
|
624
|
+
const keyName = prop.key?.type === "Identifier" ? prop.key.name : "";
|
|
625
|
+
for (const member of typeMembers) if (member.type === "TSPropertySignature" && member.key?.type === "Identifier" && member.key.name === keyName && member.typeAnnotation?.typeAnnotation) {
|
|
626
|
+
v.type = resolveTypeString(member.typeAnnotation.typeAnnotation);
|
|
627
|
+
break;
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
vars.push(v);
|
|
631
|
+
}
|
|
632
|
+
return vars;
|
|
633
|
+
}
|
|
634
|
+
if (id.type === "ArrayPattern") {
|
|
635
|
+
const vars = [];
|
|
636
|
+
for (const el of id.elements) {
|
|
637
|
+
if (!el) continue;
|
|
638
|
+
if (el.type === "Identifier") vars.push({ name: el.name });
|
|
639
|
+
else if (el.type === "RestElement" && el.argument?.type === "Identifier") vars.push({ name: el.argument.name });
|
|
640
|
+
else if (el.type === "AssignmentPattern" && el.left?.type === "Identifier") vars.push({ name: el.left.name });
|
|
641
|
+
}
|
|
642
|
+
return vars;
|
|
643
|
+
}
|
|
644
|
+
return [];
|
|
645
|
+
}
|
|
646
|
+
function extractComposables(ast, importMap, sfcDir) {
|
|
335
647
|
const seen = /* @__PURE__ */ new Set();
|
|
336
648
|
const composables = [];
|
|
337
649
|
for (const stmt of ast) {
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
seen.
|
|
341
|
-
|
|
650
|
+
if (stmt.type === "ExpressionStatement" && stmt.expression.type === "CallExpression" && stmt.expression.callee.type === "Identifier" && /^use[A-Z]/.test(stmt.expression.callee.name)) {
|
|
651
|
+
const name = stmt.expression.callee.name;
|
|
652
|
+
if (!seen.has(name)) {
|
|
653
|
+
seen.add(name);
|
|
654
|
+
composables.push({
|
|
655
|
+
name,
|
|
656
|
+
source: importMap.get(name),
|
|
657
|
+
variables: []
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
if (stmt.type === "VariableDeclaration") {
|
|
662
|
+
for (const decl of stmt.declarations) if (decl.init?.type === "CallExpression" && decl.init.callee.type === "Identifier" && /^use[A-Z]/.test(decl.init.callee.name)) {
|
|
663
|
+
const name = decl.init.callee.name;
|
|
664
|
+
if (seen.has(name)) continue;
|
|
665
|
+
seen.add(name);
|
|
666
|
+
const variables = extractVariablesFromPattern(decl);
|
|
667
|
+
const source = importMap.get(name);
|
|
668
|
+
if (variables.some((v) => !v.type) && sfcDir && source) {
|
|
669
|
+
const resolvedPath = resolveImportPath(source, sfcDir);
|
|
670
|
+
if (resolvedPath) {
|
|
671
|
+
const typeMap = resolveComposableTypes(resolvedPath, name, variables.filter((v) => !v.type).map((v) => v.name));
|
|
672
|
+
for (const v of variables) if (!v.type && typeMap.has(v.name)) v.type = typeMap.get(v.name);
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
composables.push({
|
|
676
|
+
name,
|
|
677
|
+
source,
|
|
678
|
+
variables
|
|
679
|
+
});
|
|
680
|
+
}
|
|
342
681
|
}
|
|
343
682
|
}
|
|
344
683
|
return composables;
|
|
345
684
|
}
|
|
346
|
-
function extractComposableCallNames(stmt) {
|
|
347
|
-
const names = [];
|
|
348
|
-
if (stmt.type === "ExpressionStatement" && stmt.expression.type === "CallExpression" && stmt.expression.callee.type === "Identifier" && /^use[A-Z]/.test(stmt.expression.callee.name)) names.push(stmt.expression.callee.name);
|
|
349
|
-
if (stmt.type === "VariableDeclaration") {
|
|
350
|
-
for (const decl of stmt.declarations) if (decl.init?.type === "CallExpression" && decl.init.callee.type === "Identifier" && /^use[A-Z]/.test(decl.init.callee.name)) names.push(decl.init.callee.name);
|
|
351
|
-
}
|
|
352
|
-
return names;
|
|
353
|
-
}
|
|
354
685
|
function extractTemplateSlots(templateAst) {
|
|
355
686
|
const slots = [];
|
|
356
687
|
walkTemplate(templateAst.children ?? [], slots);
|
|
@@ -448,6 +779,9 @@ function stringifyDefault(node, source) {
|
|
|
448
779
|
function esc(value) {
|
|
449
780
|
return value.replaceAll("|", "\\|");
|
|
450
781
|
}
|
|
782
|
+
function escHtml(value) {
|
|
783
|
+
return value.replaceAll("<", "<").replaceAll(">", ">");
|
|
784
|
+
}
|
|
451
785
|
function generateMarkdown(doc) {
|
|
452
786
|
const sections = [`# ${doc.name}`];
|
|
453
787
|
if (doc.description) sections.push("", doc.description);
|
|
@@ -518,8 +852,24 @@ function generateMarkdown(doc) {
|
|
|
518
852
|
}
|
|
519
853
|
}
|
|
520
854
|
if (hasComposables) {
|
|
521
|
-
sections.push("", "## Composables Used"
|
|
522
|
-
for (const c of doc.composables)
|
|
855
|
+
sections.push("", "## Composables Used");
|
|
856
|
+
for (const c of doc.composables) {
|
|
857
|
+
sections.push("", `### \`${c.name}\``);
|
|
858
|
+
if (c.source && (c.source.startsWith(".") || c.source.startsWith("@/"))) sections.push("", `*Source: \`${c.source}\`*`);
|
|
859
|
+
if (c.variables.length === 0) sections.push("", "Called for side effects.");
|
|
860
|
+
else if (!c.variables.some((v) => v.type) && c.variables.length <= 3) {
|
|
861
|
+
const vars = c.variables.map((v) => `\`${v.name}\``).join(", ");
|
|
862
|
+
sections.push("", `**Returns:** ${vars}`);
|
|
863
|
+
} else {
|
|
864
|
+
sections.push("");
|
|
865
|
+
sections.push("| Variable | Type |");
|
|
866
|
+
sections.push("| --- | --- |");
|
|
867
|
+
for (const v of c.variables) {
|
|
868
|
+
const type = v.type ? escHtml(esc(v.type)) : "-";
|
|
869
|
+
sections.push(`| ${esc(v.name)} | ${type} |`);
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
}
|
|
523
873
|
}
|
|
524
874
|
return sections.join("\n") + "\n";
|
|
525
875
|
}
|
|
@@ -527,7 +877,7 @@ function generateMarkdown(doc) {
|
|
|
527
877
|
//#region src/index.ts
|
|
528
878
|
function parseComponent(filePath) {
|
|
529
879
|
const abs = resolve(filePath);
|
|
530
|
-
return parseSFC(readFileSync(abs, "utf-8"), abs.split("/").pop() ?? "Unknown.vue");
|
|
880
|
+
return parseSFC(readFileSync(abs, "utf-8"), abs.split("/").pop() ?? "Unknown.vue", abs.substring(0, abs.lastIndexOf("/")));
|
|
531
881
|
}
|
|
532
882
|
//#endregion
|
|
533
883
|
//#region src/cli.ts
|
package/dist/index.d.mts
CHANGED
|
@@ -25,8 +25,14 @@ interface ExposeDoc {
|
|
|
25
25
|
type: string;
|
|
26
26
|
description: string;
|
|
27
27
|
}
|
|
28
|
+
interface ComposableVariable {
|
|
29
|
+
name: string;
|
|
30
|
+
type?: string;
|
|
31
|
+
}
|
|
28
32
|
interface ComposableDoc {
|
|
29
33
|
name: string;
|
|
34
|
+
source?: string;
|
|
35
|
+
variables: ComposableVariable[];
|
|
30
36
|
}
|
|
31
37
|
interface ComponentDoc {
|
|
32
38
|
name: string;
|
|
@@ -40,7 +46,7 @@ interface ComponentDoc {
|
|
|
40
46
|
}
|
|
41
47
|
//#endregion
|
|
42
48
|
//#region src/parser.d.ts
|
|
43
|
-
declare function parseSFC(source: string, filename: string): ComponentDoc;
|
|
49
|
+
declare function parseSFC(source: string, filename: string, sfcDir?: string): ComponentDoc;
|
|
44
50
|
//#endregion
|
|
45
51
|
//#region src/markdown.d.ts
|
|
46
52
|
declare function generateMarkdown(doc: ComponentDoc): string;
|
|
@@ -48,4 +54,4 @@ declare function generateMarkdown(doc: ComponentDoc): string;
|
|
|
48
54
|
//#region src/index.d.ts
|
|
49
55
|
declare function parseComponent(filePath: string): ComponentDoc;
|
|
50
56
|
//#endregion
|
|
51
|
-
export { type ComponentDoc, type ComposableDoc, type EmitDoc, type ExposeDoc, type PropDoc, type SlotDoc, generateMarkdown, parseComponent, parseSFC };
|
|
57
|
+
export { type ComponentDoc, type ComposableDoc, type ComposableVariable, type EmitDoc, type ExposeDoc, type PropDoc, type SlotDoc, generateMarkdown, parseComponent, parseSFC };
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,269 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
2
|
-
import { resolve } from "node:path";
|
|
3
|
-
import { compileScript, parse } from "@vue/compiler-sfc";
|
|
1
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
2
|
+
import { dirname, join, resolve } from "node:path";
|
|
3
|
+
import { babelParse, compileScript, parse } from "@vue/compiler-sfc";
|
|
4
|
+
//#region src/resolver.ts
|
|
5
|
+
function resolveImportPath(importSource, sfcDir) {
|
|
6
|
+
try {
|
|
7
|
+
if (!importSource.startsWith(".") && !importSource.startsWith("@/") && !importSource.startsWith("~/")) return null;
|
|
8
|
+
if (importSource.startsWith("./") || importSource.startsWith("../")) return tryResolveFile(resolve(sfcDir, importSource));
|
|
9
|
+
const tsconfig = findTsConfig(sfcDir);
|
|
10
|
+
if (!tsconfig) return null;
|
|
11
|
+
const { paths, baseUrl } = readTsConfigPaths(tsconfig);
|
|
12
|
+
if (!paths) return null;
|
|
13
|
+
const configDir = dirname(tsconfig);
|
|
14
|
+
const resolvedBaseUrl = baseUrl ? resolve(configDir, baseUrl) : configDir;
|
|
15
|
+
for (const [pattern, targets] of Object.entries(paths)) {
|
|
16
|
+
const prefix = pattern.replace(/\*$/, "");
|
|
17
|
+
if (!importSource.startsWith(prefix)) continue;
|
|
18
|
+
const remainder = importSource.slice(prefix.length);
|
|
19
|
+
for (const target of targets) {
|
|
20
|
+
const result = tryResolveFile(resolve(resolvedBaseUrl, target.replace(/\*$/, "") + remainder));
|
|
21
|
+
if (result) return result;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return null;
|
|
25
|
+
} catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function tryResolveFile(basePath) {
|
|
30
|
+
if (existsSync(basePath) && !isDirectory(basePath)) return basePath;
|
|
31
|
+
for (const ext of [".ts", ".js"]) {
|
|
32
|
+
const candidate = basePath + ext;
|
|
33
|
+
if (existsSync(candidate)) return candidate;
|
|
34
|
+
}
|
|
35
|
+
for (const ext of ["/index.ts", "/index.js"]) {
|
|
36
|
+
const candidate = basePath + ext;
|
|
37
|
+
if (existsSync(candidate)) return candidate;
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
function isDirectory(filePath) {
|
|
42
|
+
try {
|
|
43
|
+
return statSync(filePath).isDirectory();
|
|
44
|
+
} catch {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function findTsConfig(startDir) {
|
|
49
|
+
let dir = resolve(startDir);
|
|
50
|
+
const root = resolve("/");
|
|
51
|
+
while (dir !== root) {
|
|
52
|
+
const tsconfig = join(dir, "tsconfig.json");
|
|
53
|
+
if (existsSync(tsconfig)) return tsconfig;
|
|
54
|
+
const jsconfig = join(dir, "jsconfig.json");
|
|
55
|
+
if (existsSync(jsconfig)) return jsconfig;
|
|
56
|
+
const parent = dirname(dir);
|
|
57
|
+
if (parent === dir) break;
|
|
58
|
+
dir = parent;
|
|
59
|
+
}
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
function readTsConfigPaths(configPath) {
|
|
63
|
+
try {
|
|
64
|
+
const content = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
65
|
+
let paths = content.compilerOptions?.paths ?? null;
|
|
66
|
+
let baseUrl = content.compilerOptions?.baseUrl;
|
|
67
|
+
if (content.extends) {
|
|
68
|
+
const parentPath = resolve(dirname(configPath), content.extends);
|
|
69
|
+
const parentConfigFile = parentPath.endsWith(".json") ? parentPath : parentPath + ".json";
|
|
70
|
+
if (existsSync(parentConfigFile)) try {
|
|
71
|
+
const parentContent = JSON.parse(readFileSync(parentConfigFile, "utf-8"));
|
|
72
|
+
const parentPaths = parentContent.compilerOptions?.paths;
|
|
73
|
+
const parentBaseUrl = parentContent.compilerOptions?.baseUrl;
|
|
74
|
+
if (!paths && parentPaths) paths = parentPaths;
|
|
75
|
+
if (!baseUrl && parentBaseUrl) baseUrl = parentBaseUrl;
|
|
76
|
+
} catch {}
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
paths,
|
|
80
|
+
baseUrl
|
|
81
|
+
};
|
|
82
|
+
} catch {
|
|
83
|
+
return {
|
|
84
|
+
paths: null,
|
|
85
|
+
baseUrl: void 0
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function resolveComposableTypes(filePath, exportName, variableNames) {
|
|
90
|
+
try {
|
|
91
|
+
const funcNode = findExportedFunction(babelParse(readFileSync(filePath, "utf-8"), {
|
|
92
|
+
plugins: ["typescript", "jsx"],
|
|
93
|
+
sourceType: "module"
|
|
94
|
+
}).program.body, exportName);
|
|
95
|
+
if (!funcNode) return /* @__PURE__ */ new Map();
|
|
96
|
+
const body = getFunctionBody(funcNode);
|
|
97
|
+
if (!body) return /* @__PURE__ */ new Map();
|
|
98
|
+
const returnProps = findReturnProperties(body);
|
|
99
|
+
if (!returnProps) return /* @__PURE__ */ new Map();
|
|
100
|
+
const result = /* @__PURE__ */ new Map();
|
|
101
|
+
const nameSet = new Set(variableNames);
|
|
102
|
+
for (const prop of returnProps) {
|
|
103
|
+
let propName = null;
|
|
104
|
+
if (prop.type === "ObjectProperty") propName = prop.key.type === "Identifier" ? prop.key.name : prop.key.type === "StringLiteral" ? prop.key.value : null;
|
|
105
|
+
else if (prop.type === "ObjectMethod") {
|
|
106
|
+
propName = prop.key.type === "Identifier" ? prop.key.name : null;
|
|
107
|
+
if (propName && nameSet.has(propName)) result.set(propName, inferFunctionSignature(prop));
|
|
108
|
+
continue;
|
|
109
|
+
} else if (prop.type === "SpreadElement") continue;
|
|
110
|
+
if (!propName || !nameSet.has(propName)) continue;
|
|
111
|
+
if (prop.type === "ObjectProperty" && prop.shorthand) {
|
|
112
|
+
const type = traceVariableType(propName, body);
|
|
113
|
+
result.set(propName, type);
|
|
114
|
+
} else if (prop.type === "ObjectProperty") {
|
|
115
|
+
const type = inferType(prop.value);
|
|
116
|
+
result.set(propName, type);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return result;
|
|
120
|
+
} catch {
|
|
121
|
+
return /* @__PURE__ */ new Map();
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
function findExportedFunction(stmts, exportName) {
|
|
125
|
+
for (const stmt of stmts) {
|
|
126
|
+
if (stmt.type === "ExportNamedDeclaration" && stmt.declaration?.type === "FunctionDeclaration" && stmt.declaration.id?.name === exportName) return stmt.declaration;
|
|
127
|
+
if (stmt.type === "ExportNamedDeclaration" && stmt.declaration?.type === "VariableDeclaration") {
|
|
128
|
+
for (const decl of stmt.declaration.declarations) if (decl.id.type === "Identifier" && decl.id.name === exportName && decl.init && (decl.init.type === "ArrowFunctionExpression" || decl.init.type === "FunctionExpression")) return decl.init;
|
|
129
|
+
}
|
|
130
|
+
if (stmt.type === "ExportDefaultDeclaration" && stmt.declaration.type === "FunctionDeclaration" && stmt.declaration.id?.name === exportName) return stmt.declaration;
|
|
131
|
+
if (stmt.type === "ExportDefaultDeclaration" && stmt.declaration.type === "FunctionDeclaration" && !stmt.declaration.id) return stmt.declaration;
|
|
132
|
+
if (stmt.type === "ExportDefaultDeclaration" && (stmt.declaration.type === "ArrowFunctionExpression" || stmt.declaration.type === "FunctionExpression")) return stmt.declaration;
|
|
133
|
+
if (stmt.type === "FunctionDeclaration" && stmt.id?.name === exportName) {
|
|
134
|
+
if (stmts.some((s) => s.type === "ExportNamedDeclaration" && !s.declaration && s.specifiers.some((spec) => spec.type === "ExportSpecifier" && (spec.local.type === "Identifier" && spec.local.name === exportName || spec.exported.type === "Identifier" && spec.exported.name === exportName)))) return stmt;
|
|
135
|
+
}
|
|
136
|
+
if (stmt.type === "VariableDeclaration") {
|
|
137
|
+
for (const decl of stmt.declarations) if (decl.id.type === "Identifier" && decl.id.name === exportName && decl.init && (decl.init.type === "ArrowFunctionExpression" || decl.init.type === "FunctionExpression")) {
|
|
138
|
+
if (stmts.some((s) => s.type === "ExportNamedDeclaration" && !s.declaration && s.specifiers.some((spec) => spec.type === "ExportSpecifier" && (spec.local.type === "Identifier" && spec.local.name === exportName || spec.exported.type === "Identifier" && spec.exported.name === exportName)))) return decl.init;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
function getFunctionBody(node) {
|
|
145
|
+
if (node.body.type === "BlockStatement") return node.body.body;
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
function findReturnProperties(body) {
|
|
149
|
+
for (let i = body.length - 1; i >= 0; i--) {
|
|
150
|
+
const stmt = body[i];
|
|
151
|
+
if (stmt.type === "ReturnStatement" && stmt.argument?.type === "ObjectExpression") return stmt.argument.properties;
|
|
152
|
+
}
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
function traceVariableType(name, body) {
|
|
156
|
+
for (let i = body.length - 1; i >= 0; i--) {
|
|
157
|
+
const stmt = body[i];
|
|
158
|
+
if (stmt.type === "FunctionDeclaration" && stmt.id?.name === name) return inferFunctionSignature(stmt);
|
|
159
|
+
if (stmt.type === "VariableDeclaration") {
|
|
160
|
+
for (const decl of stmt.declarations) if (decl.id.type === "Identifier" && decl.id.name === name && decl.init) return inferType(decl.init);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return "unknown";
|
|
164
|
+
}
|
|
165
|
+
function inferType(node) {
|
|
166
|
+
if (node.type === "CallExpression" && node.callee.type === "Identifier" && node.callee.name === "ref") {
|
|
167
|
+
const typeParams = node.typeParameters;
|
|
168
|
+
if (typeParams?.params?.length > 0) return `Ref<${resolveTypeAnnotation(typeParams.params[0])}>`;
|
|
169
|
+
const arg = node.arguments[0];
|
|
170
|
+
if (!arg) return "Ref<unknown>";
|
|
171
|
+
return `Ref<${inferLiteralType(arg)}>`;
|
|
172
|
+
}
|
|
173
|
+
if (node.type === "CallExpression" && node.callee.type === "Identifier" && node.callee.name === "computed") return "ComputedRef";
|
|
174
|
+
if (node.type === "CallExpression" && node.callee.type === "Identifier" && node.callee.name === "reactive") return "Object";
|
|
175
|
+
if (node.type === "ArrowFunctionExpression" || node.type === "FunctionExpression") return inferFunctionSignature(node);
|
|
176
|
+
if (node.type === "CallExpression" && node.callee.type === "Identifier" && /^use[A-Z]/.test(node.callee.name)) return "unknown";
|
|
177
|
+
return inferLiteralType(node);
|
|
178
|
+
}
|
|
179
|
+
function inferLiteralType(node) {
|
|
180
|
+
switch (node.type) {
|
|
181
|
+
case "NumericLiteral": return "number";
|
|
182
|
+
case "StringLiteral": return "string";
|
|
183
|
+
case "BooleanLiteral": return "boolean";
|
|
184
|
+
case "NullLiteral": return "null";
|
|
185
|
+
case "TemplateLiteral": return "string";
|
|
186
|
+
case "ArrayExpression": return "Array";
|
|
187
|
+
case "ObjectExpression": return "Object";
|
|
188
|
+
default: return "unknown";
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
function inferFunctionSignature(node) {
|
|
192
|
+
return `(${extractParams(node.params ?? [])}) => ${extractReturnType(node)}`;
|
|
193
|
+
}
|
|
194
|
+
function extractParams(params) {
|
|
195
|
+
return params.map((param) => {
|
|
196
|
+
if (param.type === "Identifier") {
|
|
197
|
+
const annotation = param.typeAnnotation?.typeAnnotation;
|
|
198
|
+
if (annotation) return `${param.name}: ${resolveTypeAnnotation(annotation)}`;
|
|
199
|
+
return param.name;
|
|
200
|
+
}
|
|
201
|
+
if (param.type === "AssignmentPattern") {
|
|
202
|
+
const left = param.left;
|
|
203
|
+
if (left.type === "Identifier") {
|
|
204
|
+
const annotation = left.typeAnnotation?.typeAnnotation;
|
|
205
|
+
if (annotation) return `${left.name}: ${resolveTypeAnnotation(annotation)}`;
|
|
206
|
+
return left.name;
|
|
207
|
+
}
|
|
208
|
+
return "arg";
|
|
209
|
+
}
|
|
210
|
+
if (param.type === "RestElement") {
|
|
211
|
+
const arg = param.argument;
|
|
212
|
+
if (arg.type === "Identifier") {
|
|
213
|
+
const annotation = arg.typeAnnotation?.typeAnnotation;
|
|
214
|
+
if (annotation) return `...${arg.name}: ${resolveTypeAnnotation(annotation)}`;
|
|
215
|
+
return `...${arg.name}`;
|
|
216
|
+
}
|
|
217
|
+
return "...args";
|
|
218
|
+
}
|
|
219
|
+
if (param.type === "ObjectPattern") return "options";
|
|
220
|
+
if (param.type === "ArrayPattern") return "args";
|
|
221
|
+
return "arg";
|
|
222
|
+
}).join(", ");
|
|
223
|
+
}
|
|
224
|
+
function extractReturnType(node) {
|
|
225
|
+
const annotation = node.returnType?.typeAnnotation ?? node.typeAnnotation?.typeAnnotation;
|
|
226
|
+
let baseType;
|
|
227
|
+
if (annotation) baseType = resolveTypeAnnotation(annotation);
|
|
228
|
+
else baseType = "void";
|
|
229
|
+
if (node.async && baseType !== "void") return `Promise<${baseType}>`;
|
|
230
|
+
if (node.async) return "Promise<void>";
|
|
231
|
+
return baseType;
|
|
232
|
+
}
|
|
233
|
+
function resolveTypeAnnotation(node) {
|
|
234
|
+
if (!node) return "unknown";
|
|
235
|
+
switch (node.type) {
|
|
236
|
+
case "TSStringKeyword": return "string";
|
|
237
|
+
case "TSNumberKeyword": return "number";
|
|
238
|
+
case "TSBooleanKeyword": return "boolean";
|
|
239
|
+
case "TSVoidKeyword": return "void";
|
|
240
|
+
case "TSAnyKeyword": return "any";
|
|
241
|
+
case "TSNullKeyword": return "null";
|
|
242
|
+
case "TSUndefinedKeyword": return "undefined";
|
|
243
|
+
case "TSObjectKeyword": return "object";
|
|
244
|
+
case "TSNeverKeyword": return "never";
|
|
245
|
+
case "TSUnknownKeyword": return "unknown";
|
|
246
|
+
case "TSTypeReference": {
|
|
247
|
+
const name = node.typeName?.type === "Identifier" ? node.typeName.name : node.typeName?.type === "TSQualifiedName" ? `${node.typeName.left?.name ?? ""}.${node.typeName.right?.name ?? ""}` : "unknown";
|
|
248
|
+
if (node.typeParameters?.params?.length > 0) return `${name}<${node.typeParameters.params.map((p) => resolveTypeAnnotation(p)).join(", ")}>`;
|
|
249
|
+
return name;
|
|
250
|
+
}
|
|
251
|
+
case "TSUnionType": return node.types.map((t) => resolveTypeAnnotation(t)).join(" | ");
|
|
252
|
+
case "TSIntersectionType": return node.types.map((t) => resolveTypeAnnotation(t)).join(" & ");
|
|
253
|
+
case "TSArrayType": return `${resolveTypeAnnotation(node.elementType)}[]`;
|
|
254
|
+
case "TSLiteralType":
|
|
255
|
+
if (node.literal.type === "StringLiteral") return `'${node.literal.value}'`;
|
|
256
|
+
if (node.literal.type === "NumericLiteral") return String(node.literal.value);
|
|
257
|
+
if (node.literal.type === "BooleanLiteral") return String(node.literal.value);
|
|
258
|
+
return "unknown";
|
|
259
|
+
case "TSFunctionType": return "Function";
|
|
260
|
+
case "TSTupleType": return `[${(node.elementTypes ?? []).map((t) => resolveTypeAnnotation(t)).join(", ")}]`;
|
|
261
|
+
case "TSParenthesizedType": return resolveTypeAnnotation(node.typeAnnotation);
|
|
262
|
+
case "TSTypeLiteral": return "object";
|
|
263
|
+
default: return "unknown";
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
//#endregion
|
|
4
267
|
//#region src/parser.ts
|
|
5
268
|
function parseJSDocTags(comments) {
|
|
6
269
|
const result = { description: "" };
|
|
@@ -21,7 +284,7 @@ function parseJSDocTags(comments) {
|
|
|
21
284
|
result.description = descLines.join(" ");
|
|
22
285
|
return result;
|
|
23
286
|
}
|
|
24
|
-
function parseSFC(source, filename) {
|
|
287
|
+
function parseSFC(source, filename, sfcDir) {
|
|
25
288
|
const doc = {
|
|
26
289
|
name: filename.replace(/\.vue$/, "").split("/").pop() ?? "Unknown",
|
|
27
290
|
props: [],
|
|
@@ -49,7 +312,7 @@ function parseSFC(source, filename) {
|
|
|
49
312
|
else if (callee === "defineSlots" && typeParams?.params[0]?.type === "TSTypeLiteral") doc.slots = extractTypeSlots(typeParams.params[0]);
|
|
50
313
|
else if (callee === "defineExpose" && args[0]?.type === "ObjectExpression") doc.exposes = extractExposes(args[0], scriptSource);
|
|
51
314
|
}
|
|
52
|
-
doc.composables = extractComposables(setupAst);
|
|
315
|
+
doc.composables = extractComposables(setupAst, buildImportMap(setupAst), sfcDir);
|
|
53
316
|
}
|
|
54
317
|
const scriptAst = compiled.scriptAst;
|
|
55
318
|
if (scriptAst && doc.props.length === 0 && doc.emits.length === 0) {
|
|
@@ -330,26 +593,94 @@ function extractExposes(obj, _source) {
|
|
|
330
593
|
}
|
|
331
594
|
return exposes;
|
|
332
595
|
}
|
|
333
|
-
function
|
|
596
|
+
function buildImportMap(ast) {
|
|
597
|
+
const map = /* @__PURE__ */ new Map();
|
|
598
|
+
for (const stmt of ast) if (stmt.type === "ImportDeclaration") {
|
|
599
|
+
for (const spec of stmt.specifiers ?? []) if (spec.type === "ImportSpecifier" || spec.type === "ImportDefaultSpecifier") map.set(spec.local.name, stmt.source.value);
|
|
600
|
+
}
|
|
601
|
+
return map;
|
|
602
|
+
}
|
|
603
|
+
function extractVariablesFromPattern(decl) {
|
|
604
|
+
const id = decl.id;
|
|
605
|
+
if (!id) return [];
|
|
606
|
+
if (id.type === "Identifier") {
|
|
607
|
+
const v = { name: id.name };
|
|
608
|
+
if (id.typeAnnotation?.typeAnnotation) v.type = resolveTypeString(id.typeAnnotation.typeAnnotation);
|
|
609
|
+
return [v];
|
|
610
|
+
}
|
|
611
|
+
if (id.type === "ObjectPattern") {
|
|
612
|
+
const vars = [];
|
|
613
|
+
const typeAnnotation = id.typeAnnotation?.typeAnnotation;
|
|
614
|
+
const typeMembers = typeAnnotation?.type === "TSTypeLiteral" ? typeAnnotation.members : null;
|
|
615
|
+
for (const prop of id.properties) if (prop.type === "RestElement") {
|
|
616
|
+
const name = prop.argument?.name ?? "rest";
|
|
617
|
+
vars.push({ name });
|
|
618
|
+
} else if (prop.type === "ObjectProperty") {
|
|
619
|
+
const name = prop.value?.type === "Identifier" ? prop.value.name : prop.value?.type === "AssignmentPattern" && prop.value.left?.type === "Identifier" ? prop.value.left.name : prop.key?.type === "Identifier" ? prop.key.name : "";
|
|
620
|
+
if (!name) continue;
|
|
621
|
+
const v = { name };
|
|
622
|
+
if (typeMembers) {
|
|
623
|
+
const keyName = prop.key?.type === "Identifier" ? prop.key.name : "";
|
|
624
|
+
for (const member of typeMembers) if (member.type === "TSPropertySignature" && member.key?.type === "Identifier" && member.key.name === keyName && member.typeAnnotation?.typeAnnotation) {
|
|
625
|
+
v.type = resolveTypeString(member.typeAnnotation.typeAnnotation);
|
|
626
|
+
break;
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
vars.push(v);
|
|
630
|
+
}
|
|
631
|
+
return vars;
|
|
632
|
+
}
|
|
633
|
+
if (id.type === "ArrayPattern") {
|
|
634
|
+
const vars = [];
|
|
635
|
+
for (const el of id.elements) {
|
|
636
|
+
if (!el) continue;
|
|
637
|
+
if (el.type === "Identifier") vars.push({ name: el.name });
|
|
638
|
+
else if (el.type === "RestElement" && el.argument?.type === "Identifier") vars.push({ name: el.argument.name });
|
|
639
|
+
else if (el.type === "AssignmentPattern" && el.left?.type === "Identifier") vars.push({ name: el.left.name });
|
|
640
|
+
}
|
|
641
|
+
return vars;
|
|
642
|
+
}
|
|
643
|
+
return [];
|
|
644
|
+
}
|
|
645
|
+
function extractComposables(ast, importMap, sfcDir) {
|
|
334
646
|
const seen = /* @__PURE__ */ new Set();
|
|
335
647
|
const composables = [];
|
|
336
648
|
for (const stmt of ast) {
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
seen.
|
|
340
|
-
|
|
649
|
+
if (stmt.type === "ExpressionStatement" && stmt.expression.type === "CallExpression" && stmt.expression.callee.type === "Identifier" && /^use[A-Z]/.test(stmt.expression.callee.name)) {
|
|
650
|
+
const name = stmt.expression.callee.name;
|
|
651
|
+
if (!seen.has(name)) {
|
|
652
|
+
seen.add(name);
|
|
653
|
+
composables.push({
|
|
654
|
+
name,
|
|
655
|
+
source: importMap.get(name),
|
|
656
|
+
variables: []
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
if (stmt.type === "VariableDeclaration") {
|
|
661
|
+
for (const decl of stmt.declarations) if (decl.init?.type === "CallExpression" && decl.init.callee.type === "Identifier" && /^use[A-Z]/.test(decl.init.callee.name)) {
|
|
662
|
+
const name = decl.init.callee.name;
|
|
663
|
+
if (seen.has(name)) continue;
|
|
664
|
+
seen.add(name);
|
|
665
|
+
const variables = extractVariablesFromPattern(decl);
|
|
666
|
+
const source = importMap.get(name);
|
|
667
|
+
if (variables.some((v) => !v.type) && sfcDir && source) {
|
|
668
|
+
const resolvedPath = resolveImportPath(source, sfcDir);
|
|
669
|
+
if (resolvedPath) {
|
|
670
|
+
const typeMap = resolveComposableTypes(resolvedPath, name, variables.filter((v) => !v.type).map((v) => v.name));
|
|
671
|
+
for (const v of variables) if (!v.type && typeMap.has(v.name)) v.type = typeMap.get(v.name);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
composables.push({
|
|
675
|
+
name,
|
|
676
|
+
source,
|
|
677
|
+
variables
|
|
678
|
+
});
|
|
679
|
+
}
|
|
341
680
|
}
|
|
342
681
|
}
|
|
343
682
|
return composables;
|
|
344
683
|
}
|
|
345
|
-
function extractComposableCallNames(stmt) {
|
|
346
|
-
const names = [];
|
|
347
|
-
if (stmt.type === "ExpressionStatement" && stmt.expression.type === "CallExpression" && stmt.expression.callee.type === "Identifier" && /^use[A-Z]/.test(stmt.expression.callee.name)) names.push(stmt.expression.callee.name);
|
|
348
|
-
if (stmt.type === "VariableDeclaration") {
|
|
349
|
-
for (const decl of stmt.declarations) if (decl.init?.type === "CallExpression" && decl.init.callee.type === "Identifier" && /^use[A-Z]/.test(decl.init.callee.name)) names.push(decl.init.callee.name);
|
|
350
|
-
}
|
|
351
|
-
return names;
|
|
352
|
-
}
|
|
353
684
|
function extractTemplateSlots(templateAst) {
|
|
354
685
|
const slots = [];
|
|
355
686
|
walkTemplate(templateAst.children ?? [], slots);
|
|
@@ -447,6 +778,9 @@ function stringifyDefault(node, source) {
|
|
|
447
778
|
function esc(value) {
|
|
448
779
|
return value.replaceAll("|", "\\|");
|
|
449
780
|
}
|
|
781
|
+
function escHtml(value) {
|
|
782
|
+
return value.replaceAll("<", "<").replaceAll(">", ">");
|
|
783
|
+
}
|
|
450
784
|
function generateMarkdown(doc) {
|
|
451
785
|
const sections = [`# ${doc.name}`];
|
|
452
786
|
if (doc.description) sections.push("", doc.description);
|
|
@@ -517,8 +851,24 @@ function generateMarkdown(doc) {
|
|
|
517
851
|
}
|
|
518
852
|
}
|
|
519
853
|
if (hasComposables) {
|
|
520
|
-
sections.push("", "## Composables Used"
|
|
521
|
-
for (const c of doc.composables)
|
|
854
|
+
sections.push("", "## Composables Used");
|
|
855
|
+
for (const c of doc.composables) {
|
|
856
|
+
sections.push("", `### \`${c.name}\``);
|
|
857
|
+
if (c.source && (c.source.startsWith(".") || c.source.startsWith("@/"))) sections.push("", `*Source: \`${c.source}\`*`);
|
|
858
|
+
if (c.variables.length === 0) sections.push("", "Called for side effects.");
|
|
859
|
+
else if (!c.variables.some((v) => v.type) && c.variables.length <= 3) {
|
|
860
|
+
const vars = c.variables.map((v) => `\`${v.name}\``).join(", ");
|
|
861
|
+
sections.push("", `**Returns:** ${vars}`);
|
|
862
|
+
} else {
|
|
863
|
+
sections.push("");
|
|
864
|
+
sections.push("| Variable | Type |");
|
|
865
|
+
sections.push("| --- | --- |");
|
|
866
|
+
for (const v of c.variables) {
|
|
867
|
+
const type = v.type ? escHtml(esc(v.type)) : "-";
|
|
868
|
+
sections.push(`| ${esc(v.name)} | ${type} |`);
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
}
|
|
522
872
|
}
|
|
523
873
|
return sections.join("\n") + "\n";
|
|
524
874
|
}
|
|
@@ -526,7 +876,7 @@ function generateMarkdown(doc) {
|
|
|
526
876
|
//#region src/index.ts
|
|
527
877
|
function parseComponent(filePath) {
|
|
528
878
|
const abs = resolve(filePath);
|
|
529
|
-
return parseSFC(readFileSync(abs, "utf-8"), abs.split("/").pop() ?? "Unknown.vue");
|
|
879
|
+
return parseSFC(readFileSync(abs, "utf-8"), abs.split("/").pop() ?? "Unknown.vue", abs.substring(0, abs.lastIndexOf("/")));
|
|
530
880
|
}
|
|
531
881
|
//#endregion
|
|
532
882
|
export { generateMarkdown, parseComponent, parseSFC };
|