renusify 2.5.1 → 3.0.0
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/components/app/index.vue +74 -22
- package/components/app/toast/index.vue +76 -71
- package/components/app/toast/toast.vue +62 -44
- package/components/avatar/index.vue +208 -84
- package/components/button/buttonConfirm.vue +53 -26
- package/components/button/buttonGroup.js +0 -2
- package/components/button/buttonGroup.vue +310 -62
- package/components/button/index.vue +584 -100
- package/components/calendar/index.js +0 -2
- package/components/calendar/index.vue +326 -262
- package/components/calendar/month.vue +64 -55
- package/components/calendar/year.vue +30 -25
- package/components/card/index.vue +139 -59
- package/components/codeEditor/highlightCss.vue +38 -39
- package/components/codeEditor/highlightHtml.vue +64 -64
- package/components/codeEditor/highlightJs.vue +37 -38
- package/components/codeEditor/index.vue +129 -79
- package/components/codeEditor/run.vue +225 -39
- package/components/codeEditor/useCodeFormatter.js +150 -0
- package/components/confirm/index.vue +140 -81
- package/components/container/col.vue +5 -4
- package/components/container/divider.vue +28 -19
- package/components/container/index.vue +34 -15
- package/components/container/row.vue +26 -9
- package/components/container/spacer.vue +2 -4
- package/components/container/style.scss +3 -0
- package/components/content/index.vue +49 -32
- package/components/cropper/index.vue +401 -244
- package/components/float/index.vue +542 -415
- package/components/form/addressInput/index.vue +184 -109
- package/components/form/camInput/index.vue +370 -244
- package/components/form/checkInput/index.vue +138 -71
- package/components/form/checkboxInput/index.vue +87 -47
- package/components/form/colorInput/Alpha.vue +81 -83
- package/components/form/colorInput/Hue.vue +91 -68
- package/components/form/colorInput/Preview.vue +43 -47
- package/components/form/colorInput/Saturation.vue +101 -86
- package/components/form/colorInput/index.vue +72 -40
- package/components/form/colorInput/picker.vue +111 -106
- package/components/form/colorInput/useColor.js +153 -0
- package/components/form/dateInput/index.vue +691 -356
- package/components/form/dateInput/month.vue +63 -54
- package/components/form/dateInput/year.vue +35 -25
- package/components/form/fileInput/index.js +0 -1
- package/components/form/fileInput/index.vue +263 -106
- package/components/form/fileInput/single.vue +323 -164
- package/components/form/groupInput/index.vue +199 -101
- package/components/form/index.vue +189 -83
- package/components/form/input/index.vue +416 -377
- package/components/form/jsonInput/JsonView.vue +54 -56
- package/components/form/jsonInput/index.vue +247 -165
- package/components/form/maskInput/index.vue +252 -132
- package/components/form/numberInput/index.js +0 -1
- package/components/form/numberInput/index.vue +226 -117
- package/components/form/passwordInput/index.js +2 -1
- package/components/form/passwordInput/index.vue +269 -102
- package/components/form/radioInput/index.vue +143 -72
- package/components/form/rangeInput/index.vue +280 -167
- package/components/form/ratingInput/index.vue +57 -57
- package/components/form/selectInput/index.js +1 -3
- package/components/form/selectInput/index.vue +584 -296
- package/components/form/switchInput/index.vue +73 -59
- package/components/form/telInput/index.js +0 -1
- package/components/form/telInput/index.vue +238 -135
- package/components/form/textArea/index.vue +72 -35
- package/components/form/textEditor/index.vue +739 -0
- package/components/form/{text-editor → textEditor}/style.scss +8 -16
- package/components/form/textInput/index.vue +54 -32
- package/components/form/timeInput/index.vue +83 -56
- package/components/form/timeInput/range.vue +116 -95
- package/components/form/timeInput/timepicker.vue +382 -449
- package/components/form/uniqueInput/index.vue +105 -48
- package/components/form/unitInput/index.vue +139 -84
- package/components/formCreator/index.js +0 -1
- package/components/formCreator/index.vue +314 -148
- package/components/highlight/index.vue +41 -25
- package/components/highlight/style.scss +2 -2
- package/components/highlight/{mixin.js → useHighlight.js} +181 -160
- package/components/icon/index.vue +79 -33
- package/components/img/index.vue +249 -147
- package/components/img/preview.vue +180 -198
- package/components/img/svgImg.vue +42 -39
- package/components/index.js +5 -20
- package/components/infinite/index.js +1 -2
- package/components/infinite/index.vue +248 -66
- package/components/map/index.vue +428 -261
- package/components/map/route.vue +794 -487
- package/components/map/select.vue +118 -58
- package/components/menu/index.vue +201 -91
- package/components/meta/meta.js +26 -3
- package/components/modal/index.vue +383 -158
- package/components/notify/index.vue +204 -86
- package/components/notify/notification.vue +38 -55
- package/components/progress/circle.vue +189 -70
- package/components/progress/line.vue +266 -46
- package/components/searchBox/index.js +1 -3
- package/components/searchBox/index.vue +194 -101
- package/components/skeleton/index.vue +45 -20
- package/components/slider/index.vue +318 -156
- package/components/swiper/index.vue +254 -106
- package/components/table/crud/footer.vue +77 -53
- package/components/table/crud/header.vue +71 -72
- package/components/table/crud/index.vue +631 -401
- package/components/table/index.vue +721 -278
- package/components/timeAgo/index.vue +145 -96
- package/components/tour/index.vue +338 -235
- package/components/tree/index.vue +235 -89
- package/components/tree/tree-element.vue +107 -106
- package/directive/animate/index.js +77 -0
- package/directive/clickOutSide/index.js +98 -0
- package/directive/drag/index.js +153 -0
- package/directive/index.js +11 -13
- package/directive/intersect/index.js +263 -0
- package/directive/mask/index.js +67 -0
- package/directive/parallax/index.js +78 -0
- package/directive/ripple/index.js +14 -0
- package/directive/scroll/index.js +244 -0
- package/directive/sortable/index.js +274 -0
- package/directive/title/index.js +75 -0
- package/directive/touch/index.js +268 -0
- package/index.js +10 -8
- package/package.json +5 -2
- package/plugins/validation/Validate.js +88 -79
- package/scripts/generate-docs.mjs +226 -0
- package/scripts/menu.mjs +240 -0
- package/scripts/parser.mjs +1086 -0
- package/style/_index.scss +7 -0
- package/style/app.scss +13 -65
- package/style/colors.scss +5 -22
- package/style/functions/index.scss +8 -0
- package/style/mixins/index.scss +17 -5
- package/style/variables/base.scss +154 -175
- package/style/variables/color.scss +0 -12
- package/style/variables/utilities.scss +0 -180
- package/tools/helper.js +0 -8
- package/tools/icons.js +6 -1
- package/tools/root.js +71 -0
- package/components/app/style.scss +0 -41
- package/components/app/toast/style.scss +0 -20
- package/components/avatar/style.scss +0 -32
- package/components/bar/bottomNav.js +0 -1
- package/components/bar/bottomNav.vue +0 -28
- package/components/bar/bottomNavigationCircle.js +0 -2
- package/components/bar/bottomNavigationCircle.vue +0 -99
- package/components/bar/scss/bottomNav.scss +0 -67
- package/components/bar/scss/toolbar.scss +0 -174
- package/components/bar/toolbar/index.js +0 -8
- package/components/bar/toolbar/index.vue +0 -35
- package/components/bar/toolbar/laptop.vue +0 -33
- package/components/bar/toolbar/menuChilds.vue +0 -41
- package/components/bar/toolbar/menuLaptop.vue +0 -41
- package/components/bar/toolbar/menuMob.vue +0 -39
- package/components/bar/toolbar/mixin.js +0 -43
- package/components/bar/toolbar/mobile.vue +0 -34
- package/components/breadcrumb/bredcrumbItem.vue +0 -39
- package/components/breadcrumb/index.js +0 -3
- package/components/breadcrumb/index.vue +0 -71
- package/components/breadcrumb/style.scss +0 -51
- package/components/button/style.scss +0 -411
- package/components/card/style.scss +0 -86
- package/components/chart/chart.js +0 -1
- package/components/chart/chart.vue +0 -69
- package/components/chart/worldMap.js +0 -2
- package/components/chart/worldMap.vue +0 -1112
- package/components/chat/MessageList.vue +0 -163
- package/components/chat/chatInput.vue +0 -150
- package/components/chat/chatMsg.vue +0 -276
- package/components/chat/index.js +0 -11
- package/components/chat/index.vue +0 -113
- package/components/chip/index.js +0 -3
- package/components/chip/index.vue +0 -77
- package/components/chip/style.scss +0 -199
- package/components/codeEditor/mixin.js +0 -145
- package/components/countdown/index.js +0 -1
- package/components/countdown/index.vue +0 -105
- package/components/form/colorInput/mixin.js +0 -132
- package/components/form/fileInput/file.js +0 -148
- package/components/form/telInput/assets/flags.png +0 -0
- package/components/form/telInput/assets/flags@2x.png +0 -0
- package/components/form/text-editor/index.vue +0 -710
- package/components/icon/style.scss +0 -17
- package/components/infinite/div.js +0 -6
- package/components/infinite/div.vue +0 -193
- package/components/infinite/page.js +0 -3
- package/components/infinite/page.vue +0 -105
- package/components/list/index.js +0 -3
- package/components/list/index.vue +0 -122
- package/components/list/style.scss +0 -66
- package/components/message/index.js +0 -4
- package/components/message/index.vue +0 -40
- package/components/modal/style.scss +0 -146
- package/components/nestable/NestableItem.vue +0 -307
- package/components/nestable/editable.js +0 -44
- package/components/nestable/index.js +0 -1
- package/components/nestable/index.vue +0 -226
- package/components/nestable/methods.js +0 -416
- package/components/progress/style.scss +0 -229
- package/components/table/style.scss +0 -338
- package/components/tabs/index.js +0 -3
- package/components/tabs/index.vue +0 -151
- package/components/timeline/index.js +0 -6
- package/components/timeline/index.vue +0 -76
- package/directive/resize/index.js +0 -30
- package/directive/skeleton/index.js +0 -27
- package/directive/skeleton/style.scss +0 -37
- package/plugins/request/Request.js +0 -68
- package/style/animation.scss +0 -94
- package/style/style.scss +0 -8
- package/tools/rootable.js +0 -75
- /package/components/form/{text-editor → textEditor}/index.js +0 -0
- /package/components/form/{text-editor → textEditor}/preview.js +0 -0
- /package/components/form/{text-editor → textEditor}/preview.vue +0 -0
|
@@ -0,0 +1,1086 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {Object} ComponentProp
|
|
3
|
+
* @property {string} name - Prop name
|
|
4
|
+
* @property {string} [description] - Prop description from JSDoc
|
|
5
|
+
* @property {string} [type] - Prop type
|
|
6
|
+
* @property {string} [default] - Default value
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @typedef {Object} ComponentEmit
|
|
11
|
+
* @property {string} name - Emit name
|
|
12
|
+
* @property {string} [description] - Emit description
|
|
13
|
+
* @property {string} [params] - Formatted parameter documentation
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @typedef {Object} ComponentSlot
|
|
18
|
+
* @property {string} name - Slot name (defaults to 'default')
|
|
19
|
+
* @property {string} [description] - Slot description from HTML comment
|
|
20
|
+
* @property {string} [props] - Props passed to the slot
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @typedef {Object} ComponentExpose
|
|
25
|
+
* @property {string} name - Exposed property name
|
|
26
|
+
* @property {string} [description] - Description from JSDoc
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @typedef {Object} ComponentProvide
|
|
31
|
+
* @property {string} name - Provide key name
|
|
32
|
+
* @property {string} [description] - Description from JSDoc
|
|
33
|
+
* @property {string} [value] - Provided value (simplified)
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @typedef {Object} ComponentAPI
|
|
38
|
+
* @property {ComponentProp[]} props - Component props
|
|
39
|
+
* @property {ComponentEmit[]} emits - Component emits
|
|
40
|
+
* @property {ComponentSlot[]} slots - Component slots
|
|
41
|
+
* @property {ComponentExpose[]} expose - Exposed properties
|
|
42
|
+
* @property {ComponentProvide[]} provide - Provided values
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Vue Composition API Parser
|
|
47
|
+
*
|
|
48
|
+
* Parses Vue 3 script setup components and extracts their public API.
|
|
49
|
+
*/
|
|
50
|
+
export class VueComponentAPIParser {
|
|
51
|
+
/**
|
|
52
|
+
* Parse Vue component text and extract API information
|
|
53
|
+
* @param {string} componentText - Full component text including template and script
|
|
54
|
+
* @returns {ComponentAPI} Extracted component API
|
|
55
|
+
*/
|
|
56
|
+
static parse(componentText) {
|
|
57
|
+
const parser = new VueComponentAPIParser();
|
|
58
|
+
return parser.parseComponent(componentText);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Parse component text
|
|
63
|
+
* @param {string} componentText - Full component text
|
|
64
|
+
* @returns {ComponentAPI}
|
|
65
|
+
*/
|
|
66
|
+
parseComponent(componentText) {
|
|
67
|
+
const scriptContent = this.extractScriptContent(componentText);
|
|
68
|
+
const templateContent = this.extractTemplateContent(componentText);
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
example: this.parseExample(componentText),
|
|
72
|
+
props: this.parseProps(scriptContent),
|
|
73
|
+
emits: this.parseEmits(scriptContent),
|
|
74
|
+
slots: this.parseSlots(templateContent),
|
|
75
|
+
expose: this.parseExpose(scriptContent),
|
|
76
|
+
provide: this.parseProvide(scriptContent)
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Extract script setup content
|
|
82
|
+
* @param {string} componentText - Full component text
|
|
83
|
+
* @returns {string} Script content
|
|
84
|
+
* @private
|
|
85
|
+
*/
|
|
86
|
+
extractScriptContent(componentText) {
|
|
87
|
+
const match = componentText.match(/<script[^>]*setup[^>]*>([\s\S]*?)<\/script>/);
|
|
88
|
+
return match ? match[1] : '';
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Extract template content
|
|
93
|
+
* @param {string} componentText - Full component text
|
|
94
|
+
* @returns {string} Template content
|
|
95
|
+
* @private
|
|
96
|
+
*/
|
|
97
|
+
extractTemplateContent(componentText) {
|
|
98
|
+
const match = componentText.match(/<template>([\s\S]*?)<\/template>/);
|
|
99
|
+
return match ? match[1] : '';
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Parse example from the beginning of the file
|
|
104
|
+
* @param {string} componentText - Full component text
|
|
105
|
+
* @returns {Object} Object with example names as keys and {template, script, style} objects as values
|
|
106
|
+
* @private
|
|
107
|
+
*/
|
|
108
|
+
parseExample(componentText) {
|
|
109
|
+
const examples = {};
|
|
110
|
+
|
|
111
|
+
// Look for the first JSDoc comment in the file
|
|
112
|
+
const firstJsdocMatch = componentText.match(/\/\*\*([\s\S]*?)\*\//);
|
|
113
|
+
|
|
114
|
+
if (!firstJsdocMatch) return examples;
|
|
115
|
+
|
|
116
|
+
const jsdocContent = firstJsdocMatch[1];
|
|
117
|
+
|
|
118
|
+
// Find all @example sections
|
|
119
|
+
const exampleRegex = /@example\s*\/\/\s*([^\n]+)([\s\S]*?)(?=@example|\*\/)/g;
|
|
120
|
+
let match;
|
|
121
|
+
|
|
122
|
+
while ((match = exampleRegex.exec(jsdocContent)) !== null) {
|
|
123
|
+
const exampleName = match[1].trim();
|
|
124
|
+
let exampleContent = match[2].trim();
|
|
125
|
+
|
|
126
|
+
// Clean the content - remove leading * from each line
|
|
127
|
+
exampleContent = this.cleanJsDocLines(exampleContent);
|
|
128
|
+
|
|
129
|
+
const exampleObj = this.parseExampleContent(exampleContent);
|
|
130
|
+
examples[exampleName] = exampleObj;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Also handle the last example if it doesn't end with another @example
|
|
134
|
+
const lastExampleRegex = /@example\s*\/\/\s*([^\n]+)([\s\S]*)/;
|
|
135
|
+
const lastMatch = jsdocContent.match(lastExampleRegex);
|
|
136
|
+
|
|
137
|
+
if (lastMatch && !examples[lastMatch[1].trim()]) {
|
|
138
|
+
const exampleName = lastMatch[1].trim();
|
|
139
|
+
let exampleContent = lastMatch[2].replace(/\*\//, '').trim();
|
|
140
|
+
|
|
141
|
+
exampleContent = this.cleanJsDocLines(exampleContent);
|
|
142
|
+
const exampleObj = this.parseExampleContent(exampleContent);
|
|
143
|
+
examples[exampleName] = exampleObj;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return examples;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Clean JSDoc lines by removing leading * and trimming
|
|
151
|
+
* @param {string} content - Content with JSDoc markers
|
|
152
|
+
* @returns {string} Cleaned content
|
|
153
|
+
* @private
|
|
154
|
+
*/
|
|
155
|
+
cleanJsDocLines(content) {
|
|
156
|
+
return content
|
|
157
|
+
.split('\n')
|
|
158
|
+
.map(line => line.replace(/^\s*\*\s?/, '').trim())
|
|
159
|
+
.filter(line => line !== '')
|
|
160
|
+
.join('\n');
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Parse example content into template, script, and style parts
|
|
165
|
+
* @param {string} content - Raw example content
|
|
166
|
+
* @returns {Object} Object with template, script, and style properties
|
|
167
|
+
* @private
|
|
168
|
+
*/
|
|
169
|
+
parseExampleContent(content) {
|
|
170
|
+
const result = {
|
|
171
|
+
template: '',
|
|
172
|
+
script: '',
|
|
173
|
+
style: ''
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
// Fix malformed script tags
|
|
177
|
+
content = content.replace(/<\/\/script>/g, '</script>');
|
|
178
|
+
|
|
179
|
+
// First, check if the content has <template> tags
|
|
180
|
+
const templateStart = content.indexOf('<template>');
|
|
181
|
+
let templateContent = '';
|
|
182
|
+
|
|
183
|
+
if (templateStart !== -1) {
|
|
184
|
+
// Find the closing </template> tag
|
|
185
|
+
const templateEnd = this.findTagEnd(content, templateStart, 'template');
|
|
186
|
+
if (templateEnd !== -1) {
|
|
187
|
+
// Get the content inside <template> tags
|
|
188
|
+
const templateTagEnd = content.indexOf('>', templateStart) + 1;
|
|
189
|
+
templateContent = content.substring(templateTagEnd, templateEnd).trim();
|
|
190
|
+
}
|
|
191
|
+
} else {
|
|
192
|
+
// No <template> tags, extract everything before script or style
|
|
193
|
+
let templateEnd = content.length;
|
|
194
|
+
const scriptStart = content.indexOf('<script');
|
|
195
|
+
const styleStart = content.indexOf('<style');
|
|
196
|
+
|
|
197
|
+
if (scriptStart !== -1) templateEnd = Math.min(templateEnd, scriptStart);
|
|
198
|
+
if (styleStart !== -1) templateEnd = Math.min(templateEnd, styleStart);
|
|
199
|
+
|
|
200
|
+
// Also look for JavaScript keywords that might indicate script start without tags
|
|
201
|
+
const jsKeywords = ['import ', 'export ', 'const ', 'let ', 'var ', 'function ', 'class '];
|
|
202
|
+
for (const keyword of jsKeywords) {
|
|
203
|
+
const idx = content.indexOf(keyword);
|
|
204
|
+
if (idx !== -1 && idx < templateEnd) {
|
|
205
|
+
templateEnd = idx;
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
templateContent = content.substring(0, templateEnd).trim();
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
result.template = templateContent;
|
|
214
|
+
|
|
215
|
+
// Extract script content
|
|
216
|
+
const scriptStart = content.indexOf('<script');
|
|
217
|
+
if (scriptStart !== -1) {
|
|
218
|
+
// Find the end of the script tag
|
|
219
|
+
let scriptEnd = this.findTagEnd(content, scriptStart, 'script');
|
|
220
|
+
if (scriptEnd !== -1) {
|
|
221
|
+
// Get content between <script> and </script>
|
|
222
|
+
const scriptTagEnd = content.indexOf('>', scriptStart) + 1;
|
|
223
|
+
result.script = content.substring(scriptTagEnd, scriptEnd).trim();
|
|
224
|
+
}
|
|
225
|
+
} else {
|
|
226
|
+
// Look for script content without tags - after the template content
|
|
227
|
+
const afterTemplate = content.substring(content.indexOf(templateContent) + templateContent.length).trim();
|
|
228
|
+
if (afterTemplate && !afterTemplate.startsWith('<style')) {
|
|
229
|
+
// Check if it looks like script
|
|
230
|
+
let isScript = false;
|
|
231
|
+
const jsKeywords = ['import ', 'export ', 'const ', 'let ', 'var ', 'function ', 'class '];
|
|
232
|
+
for (const keyword of jsKeywords) {
|
|
233
|
+
if (afterTemplate.includes(keyword)) {
|
|
234
|
+
isScript = true;
|
|
235
|
+
break;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
if (isScript) {
|
|
239
|
+
result.script = afterScriptTag.trim();
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Extract style content
|
|
245
|
+
const styleStart = content.indexOf('<style');
|
|
246
|
+
if (styleStart !== -1) {
|
|
247
|
+
let styleEnd = this.findTagEnd(content, styleStart, 'style');
|
|
248
|
+
if (styleEnd !== -1) {
|
|
249
|
+
const styleTagEnd = content.indexOf('>', styleStart) + 1;
|
|
250
|
+
result.style = content.substring(styleTagEnd, styleEnd).trim();
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Clean indentation
|
|
255
|
+
result.template = this.cleanIndentation(result.template);
|
|
256
|
+
result.script = this.cleanIndentation(result.script);
|
|
257
|
+
result.style = this.cleanIndentation(result.style);
|
|
258
|
+
|
|
259
|
+
return result;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Find the end position of a tag (closing tag)
|
|
264
|
+
* @param {string} content - Full content
|
|
265
|
+
* @param {number} startIndex - Start index of opening tag
|
|
266
|
+
* @param {string} tagName - Tag name
|
|
267
|
+
* @returns {number} End index or -1 if not found
|
|
268
|
+
* @private
|
|
269
|
+
*/
|
|
270
|
+
findTagEnd(content, startIndex, tagName) {
|
|
271
|
+
const openingTag = `<${tagName}`;
|
|
272
|
+
const closingTag = `</${tagName}>`;
|
|
273
|
+
|
|
274
|
+
// Find the end of the opening tag
|
|
275
|
+
let tagEnd = content.indexOf('>', startIndex);
|
|
276
|
+
if (tagEnd === -1) return -1;
|
|
277
|
+
|
|
278
|
+
// Now look for the closing tag
|
|
279
|
+
let depth = 0;
|
|
280
|
+
let pos = tagEnd + 1;
|
|
281
|
+
|
|
282
|
+
while (pos < content.length) {
|
|
283
|
+
// Check for nested opening tags
|
|
284
|
+
const nextOpen = content.indexOf(openingTag, pos);
|
|
285
|
+
const nextClose = content.indexOf(closingTag, pos);
|
|
286
|
+
|
|
287
|
+
// If no closing tag found, return -1
|
|
288
|
+
if (nextClose === -1) return -1;
|
|
289
|
+
|
|
290
|
+
// If there's an opening tag before the closing tag, we have nesting
|
|
291
|
+
if (nextOpen !== -1 && nextOpen < nextClose) {
|
|
292
|
+
depth++;
|
|
293
|
+
pos = content.indexOf('>', nextOpen) + 1;
|
|
294
|
+
} else {
|
|
295
|
+
if (depth === 0) {
|
|
296
|
+
return nextClose;
|
|
297
|
+
}
|
|
298
|
+
depth--;
|
|
299
|
+
pos = nextClose + closingTag.length;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return -1;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Clean indentation from example content
|
|
309
|
+
* @param {string} content - Content with possible indentation
|
|
310
|
+
* @returns {string} Cleaned content
|
|
311
|
+
* @private
|
|
312
|
+
*/
|
|
313
|
+
cleanIndentation(content) {
|
|
314
|
+
if (!content || content.trim() === '') return '';
|
|
315
|
+
|
|
316
|
+
const lines = content.split('\n');
|
|
317
|
+
|
|
318
|
+
// Find minimum indentation of non-empty lines
|
|
319
|
+
let minIndent = Infinity;
|
|
320
|
+
for (const line of lines) {
|
|
321
|
+
if (line.trim().length === 0) continue;
|
|
322
|
+
const leadingSpaces = line.match(/^(\s*)/)[0].length;
|
|
323
|
+
minIndent = Math.min(minIndent, leadingSpaces);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (minIndent === Infinity) minIndent = 0;
|
|
327
|
+
|
|
328
|
+
// Remove minimum indentation from all lines
|
|
329
|
+
const cleanedLines = lines.map(line => {
|
|
330
|
+
if (line.trim().length === 0) return line;
|
|
331
|
+
if (line.length >= minIndent) {
|
|
332
|
+
return line.substring(minIndent);
|
|
333
|
+
}
|
|
334
|
+
return line;
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
// Remove leading and trailing empty lines
|
|
338
|
+
let start = 0;
|
|
339
|
+
let end = cleanedLines.length - 1;
|
|
340
|
+
|
|
341
|
+
while (start < cleanedLines.length && cleanedLines[start].trim() === '') start++;
|
|
342
|
+
while (end >= 0 && cleanedLines[end].trim() === '') end--;
|
|
343
|
+
|
|
344
|
+
if (start > end) return '';
|
|
345
|
+
|
|
346
|
+
return cleanedLines.slice(start, end + 1).join('\n').trim();
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Parse props from script content
|
|
351
|
+
* @param {string} scriptContent - Script setup content
|
|
352
|
+
* @returns {ComponentProp[]}
|
|
353
|
+
* @private
|
|
354
|
+
*/
|
|
355
|
+
parseProps(scriptContent) {
|
|
356
|
+
const props = [];
|
|
357
|
+
const propsMatch = scriptContent.match(/defineProps\s*\(\s*\{([\s\S]*?)\}\s*\)/);
|
|
358
|
+
|
|
359
|
+
if (!propsMatch) return props;
|
|
360
|
+
|
|
361
|
+
const propsContent = propsMatch[1];
|
|
362
|
+
const lines = propsContent.split('\n');
|
|
363
|
+
|
|
364
|
+
let currentJsdoc = null;
|
|
365
|
+
let braceDepth = 0;
|
|
366
|
+
|
|
367
|
+
for (let i = 0; i < lines.length; i++) {
|
|
368
|
+
const line = lines[i].trim();
|
|
369
|
+
|
|
370
|
+
// Track JSDoc comments
|
|
371
|
+
if (line.startsWith('/**')) {
|
|
372
|
+
currentJsdoc = this.collectJSDoc(lines, i);
|
|
373
|
+
i = currentJsdoc.endLine;
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// Check for prop definition
|
|
378
|
+
const propMatch = line.match(/^(\w+)\s*:/);
|
|
379
|
+
if (propMatch && braceDepth === 0) {
|
|
380
|
+
const propName = propMatch[1];
|
|
381
|
+
|
|
382
|
+
// Parse prop value (could be simple type or object)
|
|
383
|
+
const propValue = line.slice(propMatch[0].length).trim();
|
|
384
|
+
const propInfo = this.parsePropDefinition(propValue, lines, i);
|
|
385
|
+
|
|
386
|
+
props.push({
|
|
387
|
+
name: propName,
|
|
388
|
+
description: currentJsdoc ? this.cleanJSDocText(currentJsdoc.text) : undefined,
|
|
389
|
+
type: propInfo.type,
|
|
390
|
+
default: propInfo.default
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
currentJsdoc = null;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Track brace depth for nested objects
|
|
397
|
+
braceDepth += (line.match(/{/g) || []).length;
|
|
398
|
+
braceDepth -= (line.match(/}/g) || []).length;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
return props;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Parse a single prop definition
|
|
406
|
+
* @param {string} propValue - Prop value string
|
|
407
|
+
* @param {string[]} lines - All lines
|
|
408
|
+
* @param {number} startLine - Starting line index
|
|
409
|
+
* @returns {{type: string, default: string}}
|
|
410
|
+
* @private
|
|
411
|
+
*/
|
|
412
|
+
parsePropDefinition(propValue, lines, startLine) {
|
|
413
|
+
const result = {type: '', default: 'undefined'};
|
|
414
|
+
|
|
415
|
+
// Helper function to clean and normalize type string
|
|
416
|
+
const normalizeType = (typeStr) => {
|
|
417
|
+
return typeStr.trim().replace(/,$/, '').replace(/\s+/g, ' ');
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
// If it's a simple type (Boolean, String, etc.)
|
|
421
|
+
if (!propValue.startsWith('{')) {
|
|
422
|
+
result.type = normalizeType(propValue);
|
|
423
|
+
return result;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// Parse prop configuration object
|
|
427
|
+
let configContent = propValue;
|
|
428
|
+
let braceCount = (propValue.match(/{/g) || []).length - (propValue.match(/}/g) || []).length;
|
|
429
|
+
let lineIndex = startLine;
|
|
430
|
+
|
|
431
|
+
// Collect multi-line prop configuration
|
|
432
|
+
while (braceCount > 0 && lineIndex + 1 < lines.length) {
|
|
433
|
+
lineIndex++;
|
|
434
|
+
const nextLine = lines[lineIndex];
|
|
435
|
+
configContent += '\n' + nextLine;
|
|
436
|
+
braceCount += (nextLine.match(/{/g) || []).length;
|
|
437
|
+
braceCount -= (nextLine.match(/}/g) || []).length;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// Now parse the config object properties
|
|
441
|
+
// Remove the outer braces
|
|
442
|
+
const innerContent = configContent.substring(1, configContent.lastIndexOf('}')).trim();
|
|
443
|
+
|
|
444
|
+
// Split by properties (looking for key: value pairs)
|
|
445
|
+
// This is a simplified parser - we'll look for type and default specifically
|
|
446
|
+
const properties = [];
|
|
447
|
+
let currentProp = '';
|
|
448
|
+
let inBrackets = 0;
|
|
449
|
+
let inBraces = 0;
|
|
450
|
+
let inParens = 0;
|
|
451
|
+
let inString = false;
|
|
452
|
+
let stringChar = '';
|
|
453
|
+
|
|
454
|
+
for (let i = 0; i < innerContent.length; i++) {
|
|
455
|
+
const char = innerContent[i];
|
|
456
|
+
const prevChar = i > 0 ? innerContent[i - 1] : '';
|
|
457
|
+
|
|
458
|
+
// Handle strings
|
|
459
|
+
if (!inString && (char === "'" || char === '"' || char === '`')) {
|
|
460
|
+
inString = true;
|
|
461
|
+
stringChar = char;
|
|
462
|
+
} else if (inString && char === stringChar && prevChar !== '\\') {
|
|
463
|
+
inString = false;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// Only track brackets/braces/parens when not in a string
|
|
467
|
+
if (!inString) {
|
|
468
|
+
if (char === '[') inBrackets++;
|
|
469
|
+
else if (char === ']') inBrackets--;
|
|
470
|
+
else if (char === '{') inBraces++;
|
|
471
|
+
else if (char === '}') inBraces--;
|
|
472
|
+
else if (char === '(') inParens++;
|
|
473
|
+
else if (char === ')') inParens--;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// Check for property separator (comma) at top level
|
|
477
|
+
if (char === ',' && !inString && inBrackets === 0 && inBraces === 0 && inParens === 0) {
|
|
478
|
+
if (currentProp.trim()) {
|
|
479
|
+
properties.push(currentProp.trim());
|
|
480
|
+
currentProp = '';
|
|
481
|
+
}
|
|
482
|
+
} else {
|
|
483
|
+
currentProp += char;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// Add the last property
|
|
488
|
+
if (currentProp.trim()) {
|
|
489
|
+
properties.push(currentProp.trim());
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// Now parse each property
|
|
493
|
+
for (const prop of properties) {
|
|
494
|
+
const colonIndex = prop.indexOf(':');
|
|
495
|
+
if (colonIndex === -1) continue;
|
|
496
|
+
|
|
497
|
+
const key = prop.substring(0, colonIndex).trim();
|
|
498
|
+
const value = prop.substring(colonIndex + 1).trim();
|
|
499
|
+
|
|
500
|
+
if (key === 'type') {
|
|
501
|
+
let typeValue = value;
|
|
502
|
+
|
|
503
|
+
// Clean up trailing comma if present
|
|
504
|
+
typeValue = typeValue.replace(/,$/, '').trim();
|
|
505
|
+
|
|
506
|
+
// Parse the type
|
|
507
|
+
result.type = this.parseTypeValue(typeValue);
|
|
508
|
+
} else if (key === 'default') {
|
|
509
|
+
let defaultValue = value;
|
|
510
|
+
|
|
511
|
+
// Clean up trailing comma if present
|
|
512
|
+
defaultValue = defaultValue.replace(/,$/, '').trim();
|
|
513
|
+
|
|
514
|
+
// Handle quoted strings
|
|
515
|
+
if ((defaultValue.startsWith("'") && defaultValue.endsWith("'")) ||
|
|
516
|
+
(defaultValue.startsWith('"') && defaultValue.endsWith('"'))) {
|
|
517
|
+
result.default = defaultValue.substring(1, defaultValue.length - 1);
|
|
518
|
+
} else {
|
|
519
|
+
result.default = defaultValue;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
return result;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/**
|
|
528
|
+
* Parse type value from prop definition
|
|
529
|
+
* @param {string} typeValue - Raw type value
|
|
530
|
+
* @returns {string} Formatted type
|
|
531
|
+
* @private
|
|
532
|
+
*/
|
|
533
|
+
parseTypeValue(typeValue) {
|
|
534
|
+
// Clean the value
|
|
535
|
+
typeValue = typeValue.trim();
|
|
536
|
+
|
|
537
|
+
// Handle array types: [String, Number]
|
|
538
|
+
if (typeValue.startsWith('[') && typeValue.endsWith(']')) {
|
|
539
|
+
const innerTypes = typeValue
|
|
540
|
+
.substring(1, typeValue.length - 1)
|
|
541
|
+
.split(',')
|
|
542
|
+
.map(t => t.trim())
|
|
543
|
+
.filter(t => t.length > 0);
|
|
544
|
+
|
|
545
|
+
if (innerTypes.length === 0) {
|
|
546
|
+
return 'Array';
|
|
547
|
+
} else if (innerTypes.length === 1) {
|
|
548
|
+
return innerTypes[0];
|
|
549
|
+
} else {
|
|
550
|
+
// For multiple types, show as union
|
|
551
|
+
return innerTypes.join(' | ');
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// Handle union types
|
|
556
|
+
if (typeValue.includes('|')) {
|
|
557
|
+
const types = typeValue.split('|')
|
|
558
|
+
.map(t => t.trim())
|
|
559
|
+
.filter(t => t.length > 0);
|
|
560
|
+
|
|
561
|
+
if (types.length === 1) {
|
|
562
|
+
return types[0];
|
|
563
|
+
}
|
|
564
|
+
return types.join(' | ');
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// Handle intersection types
|
|
568
|
+
if (typeValue.includes('&')) {
|
|
569
|
+
const types = typeValue.split('&')
|
|
570
|
+
.map(t => t.trim())
|
|
571
|
+
.filter(t => t.length > 0);
|
|
572
|
+
|
|
573
|
+
if (types.length === 1) {
|
|
574
|
+
return types[0];
|
|
575
|
+
}
|
|
576
|
+
return types.join(' & ');
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// Handle common types
|
|
580
|
+
const typeMap = {
|
|
581
|
+
'String': 'String',
|
|
582
|
+
'Number': 'Number',
|
|
583
|
+
'Boolean': 'Boolean',
|
|
584
|
+
'Array': 'Array',
|
|
585
|
+
'Object': 'Object',
|
|
586
|
+
'Function': 'Function',
|
|
587
|
+
'Symbol': 'Symbol',
|
|
588
|
+
'Date': 'Date',
|
|
589
|
+
'Promise': 'Promise',
|
|
590
|
+
'RegExp': 'RegExp',
|
|
591
|
+
'Error': 'Error'
|
|
592
|
+
};
|
|
593
|
+
|
|
594
|
+
return typeMap[typeValue] || typeValue;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
/**
|
|
598
|
+
* Parse emits from script content
|
|
599
|
+
* @param {string} scriptContent - Script setup content
|
|
600
|
+
* @returns {ComponentEmit[]}
|
|
601
|
+
* @private
|
|
602
|
+
*/
|
|
603
|
+
parseEmits(scriptContent) {
|
|
604
|
+
const emits = [];
|
|
605
|
+
const emitsMatch = scriptContent.match(/defineEmits\s*\(\s*\[([\s\S]*?)\]\s*\)/);
|
|
606
|
+
|
|
607
|
+
if (!emitsMatch) return emits;
|
|
608
|
+
|
|
609
|
+
const emitsContent = emitsMatch[1];
|
|
610
|
+
const lines = emitsContent.split('\n');
|
|
611
|
+
|
|
612
|
+
let currentJsdoc = null;
|
|
613
|
+
|
|
614
|
+
for (let i = 0; i < lines.length; i++) {
|
|
615
|
+
const line = lines[i].trim();
|
|
616
|
+
|
|
617
|
+
// Collect JSDoc
|
|
618
|
+
if (line.startsWith('/**')) {
|
|
619
|
+
currentJsdoc = this.collectJSDoc(lines, i);
|
|
620
|
+
i = currentJsdoc.endLine;
|
|
621
|
+
continue;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// Find emit names in quotes
|
|
625
|
+
const emitNames = this.extractQuotedStrings(line);
|
|
626
|
+
|
|
627
|
+
for (const emitName of emitNames) {
|
|
628
|
+
if (!emitName || emitName.includes('*')) continue;
|
|
629
|
+
|
|
630
|
+
const params = currentJsdoc ? this.extractJSDocParams(currentJsdoc.text) : null;
|
|
631
|
+
|
|
632
|
+
emits.push({
|
|
633
|
+
name: emitName,
|
|
634
|
+
description: currentJsdoc ? this.cleanJSDocText(currentJsdoc.text, true) : undefined,
|
|
635
|
+
params: params ? this.formatParams(params) : undefined
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// Reset JSDoc after processing emits
|
|
640
|
+
if (emitNames.length > 0) {
|
|
641
|
+
currentJsdoc = null;
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
return emits.filter(emit => emit.name && emit.name.trim().length > 0);
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
/**
|
|
649
|
+
* Parse slots from template content
|
|
650
|
+
* @param {string} templateContent - Template content
|
|
651
|
+
* @returns {ComponentSlot[]}
|
|
652
|
+
* @private
|
|
653
|
+
*/
|
|
654
|
+
parseSlots(templateContent) {
|
|
655
|
+
const slots = [];
|
|
656
|
+
if (!templateContent) return slots;
|
|
657
|
+
|
|
658
|
+
// Find all slot elements along with their preceding comments
|
|
659
|
+
const slotWithCommentRegex = /((?:<!--[\s\S]*?-->)?\s*)<slot\b([^>]*)>/g;
|
|
660
|
+
let match;
|
|
661
|
+
|
|
662
|
+
while ((match = slotWithCommentRegex.exec(templateContent)) !== null) {
|
|
663
|
+
const [fullMatch, whitespaceAndComment, slotAttrs] = match;
|
|
664
|
+
const slotName = this.extractSlotName(slotAttrs);
|
|
665
|
+
const slotProps = this.extractSlotProps(slotAttrs);
|
|
666
|
+
|
|
667
|
+
// Parse the comment if present
|
|
668
|
+
const commentMatch = whitespaceAndComment.match(/<!--([\s\S]*?)-->/);
|
|
669
|
+
let description = '';
|
|
670
|
+
let example = '';
|
|
671
|
+
|
|
672
|
+
if (commentMatch) {
|
|
673
|
+
const commentContent = commentMatch[1].trim();
|
|
674
|
+
const parsedComment = this.parseSlotComment(commentContent);
|
|
675
|
+
description = parsedComment.description;
|
|
676
|
+
example = parsedComment.example;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
slots.push({
|
|
680
|
+
name: slotName,
|
|
681
|
+
description: description || undefined,
|
|
682
|
+
props: slotProps,
|
|
683
|
+
example: example || undefined
|
|
684
|
+
});
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
return slots;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
/**
|
|
691
|
+
* Parse slot comment content to extract description and example
|
|
692
|
+
* @param {string} commentContent - Comment content without <!-- and -->
|
|
693
|
+
* @returns {{description: string, example: string}}
|
|
694
|
+
* @private
|
|
695
|
+
*/
|
|
696
|
+
parseSlotComment(commentContent) {
|
|
697
|
+
let description = '';
|
|
698
|
+
let example = '';
|
|
699
|
+
|
|
700
|
+
// Split into lines and clean
|
|
701
|
+
const lines = commentContent.split('\n')
|
|
702
|
+
.map(line => line.trim())
|
|
703
|
+
.filter(line => line.length > 0);
|
|
704
|
+
|
|
705
|
+
// Remove leading * from each line if it's a JSDoc-style comment
|
|
706
|
+
const cleanedLines = lines.map(line => line.replace(/^\s*\*\s?/, ''));
|
|
707
|
+
|
|
708
|
+
// Join back and parse
|
|
709
|
+
const fullText = cleanedLines.join('\n');
|
|
710
|
+
|
|
711
|
+
// Split by @example tag
|
|
712
|
+
const parts = fullText.split(/@example\s*/);
|
|
713
|
+
|
|
714
|
+
if (parts.length > 0) {
|
|
715
|
+
description = parts[0].trim();
|
|
716
|
+
|
|
717
|
+
// If there's more content after @example
|
|
718
|
+
if (parts.length > 1) {
|
|
719
|
+
example = parts.slice(1).join('\n').trim();
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
return {description, example};
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
/**
|
|
727
|
+
* Extract slot name from attributes
|
|
728
|
+
* @param {string} attrs - Slot attributes string
|
|
729
|
+
* @returns {string}
|
|
730
|
+
* @private
|
|
731
|
+
*/
|
|
732
|
+
extractSlotName(attrs) {
|
|
733
|
+
const nameMatch = attrs.match(/name\s*=\s*["']([^"']+)["']/);
|
|
734
|
+
return nameMatch ? nameMatch[1] : 'default';
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
/**
|
|
738
|
+
* Extract slot props from attributes
|
|
739
|
+
* @param {string} attrs - Slot attributes string
|
|
740
|
+
* @returns {string|undefined}
|
|
741
|
+
* @private
|
|
742
|
+
*/
|
|
743
|
+
extractSlotProps(attrs) {
|
|
744
|
+
const props = [];
|
|
745
|
+
|
|
746
|
+
// Check for v-bind
|
|
747
|
+
const bindMatch = attrs.match(/v-bind\s*=\s*["']([^"']+)["']/);
|
|
748
|
+
if (bindMatch) {
|
|
749
|
+
return `v-bind="${bindMatch[1]}"`;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
// Check for individual bindings
|
|
753
|
+
const bindingRegex = /:(\w+)\s*=\s*["']([^"']+)["']/g;
|
|
754
|
+
let bindingMatch;
|
|
755
|
+
|
|
756
|
+
while ((bindingMatch = bindingRegex.exec(attrs)) !== null) {
|
|
757
|
+
props.push(`${bindingMatch[1]}: ${bindingMatch[2]}`);
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
return props.length > 0 ? props.join(', ') : undefined;
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
/**
|
|
764
|
+
* Parse exposed properties
|
|
765
|
+
* @param {string} scriptContent - Script setup content
|
|
766
|
+
* @returns {ComponentExpose[]}
|
|
767
|
+
* @private
|
|
768
|
+
*/
|
|
769
|
+
parseExpose(scriptContent) {
|
|
770
|
+
const exposed = [];
|
|
771
|
+
|
|
772
|
+
// Find defineExpose call
|
|
773
|
+
const exposeRegex = /defineExpose\s*\(\s*\{([\s\S]*?)\}\s*\)/;
|
|
774
|
+
const exposeMatch = scriptContent.match(exposeRegex);
|
|
775
|
+
|
|
776
|
+
if (!exposeMatch) return exposed;
|
|
777
|
+
|
|
778
|
+
const exposeContent = exposeMatch[1];
|
|
779
|
+
const lines = exposeContent.split('\n');
|
|
780
|
+
|
|
781
|
+
let currentJsdoc = null;
|
|
782
|
+
let inJsdoc = false;
|
|
783
|
+
let jsdocLines = [];
|
|
784
|
+
|
|
785
|
+
for (let i = 0; i < lines.length; i++) {
|
|
786
|
+
const line = lines[i].trim();
|
|
787
|
+
|
|
788
|
+
// Handle JSDoc comments
|
|
789
|
+
if (line.startsWith('/**')) {
|
|
790
|
+
inJsdoc = true;
|
|
791
|
+
jsdocLines = [line];
|
|
792
|
+
|
|
793
|
+
// Collect the rest of the JSDoc
|
|
794
|
+
let j = i + 1;
|
|
795
|
+
while (j < lines.length && !lines[j].includes('*/')) {
|
|
796
|
+
jsdocLines.push(lines[j]);
|
|
797
|
+
j++;
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
if (j < lines.length && lines[j].includes('*/')) {
|
|
801
|
+
jsdocLines.push(lines[j]);
|
|
802
|
+
i = j; // Skip to end of JSDoc
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
currentJsdoc = jsdocLines.join('\n');
|
|
806
|
+
continue;
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
// Skip empty lines and closing braces
|
|
810
|
+
if (!line || line === '}' || line === '},') {
|
|
811
|
+
continue;
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
// Handle property definitions
|
|
815
|
+
// This can be:
|
|
816
|
+
// 1. Simple property: propName,
|
|
817
|
+
// 2. Aliased property: alias:propName,
|
|
818
|
+
// 3. Method property: propName(),
|
|
819
|
+
const propertyMatch = this.matchExposedProperty(line);
|
|
820
|
+
|
|
821
|
+
if (propertyMatch) {
|
|
822
|
+
const {name, alias} = propertyMatch;
|
|
823
|
+
|
|
824
|
+
exposed.push({
|
|
825
|
+
name: alias || name,
|
|
826
|
+
description: currentJsdoc ? this.cleanJSDocText(currentJsdoc) : undefined
|
|
827
|
+
});
|
|
828
|
+
|
|
829
|
+
// Reset JSDoc for next property
|
|
830
|
+
currentJsdoc = null;
|
|
831
|
+
inJsdoc = false;
|
|
832
|
+
jsdocLines = [];
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
// If we're not in JSDoc and currentJsdoc exists but we didn't find a property,
|
|
836
|
+
// reset it (might be orphaned JSDoc)
|
|
837
|
+
if (!inJsdoc && currentJsdoc && !propertyMatch) {
|
|
838
|
+
// Check if this line has content that suggests the JSDoc wasn't for a property
|
|
839
|
+
if (line && !line.startsWith('*') && !line.startsWith('//')) {
|
|
840
|
+
currentJsdoc = null;
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
return exposed;
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
/**
|
|
849
|
+
* Match an exposed property from a line
|
|
850
|
+
* Handles: propName, alias:propName, propName(), alias:propName()
|
|
851
|
+
* @param {string} line - Line to parse
|
|
852
|
+
* @returns {{name: string, alias?: string}|null}
|
|
853
|
+
* @private
|
|
854
|
+
*/
|
|
855
|
+
matchExposedProperty(line) {
|
|
856
|
+
// Remove trailing comma if present
|
|
857
|
+
let cleanLine = line.replace(/,$/, '').trim();
|
|
858
|
+
|
|
859
|
+
// Pattern 1: Simple property (propName)
|
|
860
|
+
const simplePropRegex = /^(\w+)$/;
|
|
861
|
+
let match = cleanLine.match(simplePropRegex);
|
|
862
|
+
if (match) {
|
|
863
|
+
return {name: match[1]};
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
// Pattern 2: Aliased property (alias:propName)
|
|
867
|
+
const aliasedPropRegex = /^(\w+)\s*:\s*(\w+)$/;
|
|
868
|
+
match = cleanLine.match(aliasedPropRegex);
|
|
869
|
+
if (match) {
|
|
870
|
+
return {name: match[2], alias: match[1]};
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
// Pattern 3: Method property (propName())
|
|
874
|
+
const methodPropRegex = /^(\w+)\(\s*\)$/;
|
|
875
|
+
match = cleanLine.match(methodPropRegex);
|
|
876
|
+
if (match) {
|
|
877
|
+
return {name: match[1]};
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
// Pattern 4: Aliased method property (alias:propName())
|
|
881
|
+
const aliasedMethodRegex = /^(\w+)\s*:\s*(\w+)\(\s*\)$/;
|
|
882
|
+
match = cleanLine.match(aliasedMethodRegex);
|
|
883
|
+
if (match) {
|
|
884
|
+
return {name: match[2], alias: match[1]};
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
// Pattern 5: Property with type annotation (propName: type)
|
|
888
|
+
// This might appear in TypeScript components
|
|
889
|
+
const typedPropRegex = /^(\w+)\s*:\s*\w+(?:<[^>]+>)?$/;
|
|
890
|
+
match = cleanLine.match(typedPropRegex);
|
|
891
|
+
if (match) {
|
|
892
|
+
return {name: match[1]};
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
// Pattern 6: Complex property (could be destructuring or computed)
|
|
896
|
+
// Look for any word that's not a JavaScript keyword
|
|
897
|
+
const wordMatch = cleanLine.match(/^(\w+)/);
|
|
898
|
+
if (wordMatch) {
|
|
899
|
+
const potentialName = wordMatch[1];
|
|
900
|
+
const jsKeywords = [
|
|
901
|
+
'if', 'else', 'for', 'while', 'function', 'return', 'const', 'let', 'var',
|
|
902
|
+
'import', 'export', 'default', 'class', 'extends', 'super', 'this', 'new',
|
|
903
|
+
'typeof', 'instanceof', 'void', 'delete', 'in', 'of', 'await', 'async',
|
|
904
|
+
'yield', 'debugger', 'with', 'switch', 'case', 'break', 'continue', 'try',
|
|
905
|
+
'catch', 'finally', 'throw'
|
|
906
|
+
];
|
|
907
|
+
|
|
908
|
+
if (!jsKeywords.includes(potentialName)) {
|
|
909
|
+
return {name: potentialName};
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
return null;
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
/**
|
|
917
|
+
* Parse provide statements
|
|
918
|
+
* @param {string} scriptContent - Script setup content
|
|
919
|
+
* @returns {ComponentProvide[]}
|
|
920
|
+
* @private
|
|
921
|
+
*/
|
|
922
|
+
parseProvide(scriptContent) {
|
|
923
|
+
const provides = [];
|
|
924
|
+
const provideRegex = /provide\s*\(\s*['"]([^'"]+)['"]\s*,\s*([^);]+)/g;
|
|
925
|
+
let match;
|
|
926
|
+
|
|
927
|
+
while ((match = provideRegex.exec(scriptContent)) !== null) {
|
|
928
|
+
const name = match[1];
|
|
929
|
+
const value = match[2].trim();
|
|
930
|
+
const description = this.findPrecedingJSDoc(scriptContent, match.index);
|
|
931
|
+
|
|
932
|
+
provides.push({
|
|
933
|
+
name: name,
|
|
934
|
+
description: description ? this.cleanJSDocText(description) : undefined,
|
|
935
|
+
value: value.replace(/\s+/g, ' ').trim()
|
|
936
|
+
});
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
return provides;
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
/**
|
|
943
|
+
* Collect a complete JSDoc comment
|
|
944
|
+
* @param {string[]} lines - Array of lines
|
|
945
|
+
* @param {number} startLine - Starting line index
|
|
946
|
+
* @returns {{text: string, endLine: number}}
|
|
947
|
+
* @private
|
|
948
|
+
*/
|
|
949
|
+
collectJSDoc(lines, startLine) {
|
|
950
|
+
let jsdocText = lines[startLine];
|
|
951
|
+
let currentLine = startLine;
|
|
952
|
+
|
|
953
|
+
while (currentLine < lines.length && !lines[currentLine].includes('*/')) {
|
|
954
|
+
currentLine++;
|
|
955
|
+
if (currentLine < lines.length) {
|
|
956
|
+
jsdocText += '\n' + lines[currentLine];
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
return {
|
|
961
|
+
text: jsdocText,
|
|
962
|
+
endLine: currentLine
|
|
963
|
+
};
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
/**
|
|
967
|
+
* Find JSDoc comment preceding a position
|
|
968
|
+
* @param {string} text - Full text
|
|
969
|
+
* @param {number} position - Position to search before
|
|
970
|
+
* @returns {string|null}
|
|
971
|
+
* @private
|
|
972
|
+
*/
|
|
973
|
+
findPrecedingJSDoc(text, position) {
|
|
974
|
+
const before = text.substring(0, position);
|
|
975
|
+
const lines = before.split('\n');
|
|
976
|
+
|
|
977
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
978
|
+
if (lines[i].includes('/**')) {
|
|
979
|
+
// Collect the complete comment
|
|
980
|
+
let jsdoc = lines[i];
|
|
981
|
+
let j = i + 1;
|
|
982
|
+
|
|
983
|
+
while (j < lines.length && !lines[j].includes('*/')) {
|
|
984
|
+
jsdoc += '\n' + lines[j];
|
|
985
|
+
j++;
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
if (j < lines.length && lines[j].includes('*/')) {
|
|
989
|
+
jsdoc += '\n' + lines[j];
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
return jsdoc;
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
return null;
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
/**
|
|
1000
|
+
* Clean JSDoc text
|
|
1001
|
+
* @param {string} jsdocText - Raw JSDoc text
|
|
1002
|
+
* @param {boolean} removeParams - Whether to remove @param tags
|
|
1003
|
+
* @returns {string}
|
|
1004
|
+
* @private
|
|
1005
|
+
*/
|
|
1006
|
+
cleanJSDocText(jsdocText, removeParams = false) {
|
|
1007
|
+
if (!jsdocText) return '';
|
|
1008
|
+
|
|
1009
|
+
// Extract content between /** and */
|
|
1010
|
+
const match = jsdocText.match(/\/\*\*\s*([\s\S]*?)\s*\*\//);
|
|
1011
|
+
if (!match) return '';
|
|
1012
|
+
|
|
1013
|
+
let content = match[1];
|
|
1014
|
+
|
|
1015
|
+
// Remove leading * from each line and trim
|
|
1016
|
+
const lines = content.split('\n');
|
|
1017
|
+
const cleanedLines = lines.map(line => {
|
|
1018
|
+
return line.replace(/^\s*\*\s?/, '').trim();
|
|
1019
|
+
}).filter(line => line.length > 0);
|
|
1020
|
+
|
|
1021
|
+
// Join and optionally remove @param tags
|
|
1022
|
+
let result = cleanedLines.join(' ').trim();
|
|
1023
|
+
|
|
1024
|
+
if (removeParams) {
|
|
1025
|
+
result = result.replace(/@param\s+\{[^}]+\}\s+\w+(?:\s+-\s+[^@]*)?/g, '').trim();
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
return result;
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
/**
|
|
1032
|
+
* Extract @param tags from JSDoc
|
|
1033
|
+
* @param {string} jsdocText - JSDoc text
|
|
1034
|
+
* @returns {Array<{type: string, name: string, description: string}>}
|
|
1035
|
+
* @private
|
|
1036
|
+
*/
|
|
1037
|
+
extractJSDocParams(jsdocText) {
|
|
1038
|
+
const params = [];
|
|
1039
|
+
const paramRegex = /@param\s+{([^}]+)}\s+([^\s-]+)(?:\s+-\s+(.*))?/g;
|
|
1040
|
+
let match;
|
|
1041
|
+
|
|
1042
|
+
while ((match = paramRegex.exec(jsdocText)) !== null) {
|
|
1043
|
+
params.push({
|
|
1044
|
+
type: match[1].trim(),
|
|
1045
|
+
name: match[2].trim(),
|
|
1046
|
+
description: match[3] ? match[3].trim() : ''
|
|
1047
|
+
});
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
return params;
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
/**
|
|
1054
|
+
* Format parameters for display
|
|
1055
|
+
* @param {Array<{type: string, name: string, description: string}>} params
|
|
1056
|
+
* @returns {string}
|
|
1057
|
+
* @private
|
|
1058
|
+
*/
|
|
1059
|
+
formatParams(params) {
|
|
1060
|
+
return params.map(param =>
|
|
1061
|
+
`@param {${param.type}} ${param.name}${param.description ? ` - ${param.description}` : ''}`
|
|
1062
|
+
).join('\n');
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
/**
|
|
1066
|
+
* Extract quoted strings from text
|
|
1067
|
+
* @param {string} text - Text to search
|
|
1068
|
+
* @returns {string[]}
|
|
1069
|
+
* @private
|
|
1070
|
+
*/
|
|
1071
|
+
extractQuotedStrings(text) {
|
|
1072
|
+
const strings = [];
|
|
1073
|
+
const regex = /['"]([^'"]+)['"]/g;
|
|
1074
|
+
let match;
|
|
1075
|
+
|
|
1076
|
+
while ((match = regex.exec(text)) !== null) {
|
|
1077
|
+
strings.push(match[1]);
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
return strings;
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
1085
|
+
module.exports = VueComponentAPIParser;
|
|
1086
|
+
}
|