sveld 0.24.9 → 0.25.1
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/lib/ComponentParser.js
CHANGED
|
@@ -5,6 +5,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
5
5
|
const comment_parser_1 = require("comment-parser");
|
|
6
6
|
const compiler_1 = require("svelte/compiler");
|
|
7
7
|
const element_tag_map_1 = require("./element-tag-map");
|
|
8
|
+
const COMMENT_BLOCK_DESCRIPTION_REGEX = /^-\s*/;
|
|
8
9
|
const VAR_DECLARATION_REGEX = /(?:const|let|function)\s+(\w+)\s*[=(]/;
|
|
9
10
|
const DEFAULT_SLOT_NAME = null;
|
|
10
11
|
const TYPEDEF_END_REGEX = /(\}|\};)$/;
|
|
@@ -185,7 +186,9 @@ class ComponentParser {
|
|
|
185
186
|
}
|
|
186
187
|
}
|
|
187
188
|
parseCustomTypes() {
|
|
188
|
-
for (const { tags, description: commentDescription } of (0, comment_parser_1.parse)(this.source, {
|
|
189
|
+
for (const { tags, description: commentDescription, source: blockSource } of (0, comment_parser_1.parse)(this.source, {
|
|
190
|
+
spacing: "preserve",
|
|
191
|
+
})) {
|
|
189
192
|
let currentEventName;
|
|
190
193
|
let currentEventType;
|
|
191
194
|
let currentEventDescription;
|
|
@@ -194,6 +197,64 @@ class ComponentParser {
|
|
|
194
197
|
let currentTypedefType;
|
|
195
198
|
let currentTypedefDescription;
|
|
196
199
|
const typedefProperties = [];
|
|
200
|
+
// Track if we've used the comment block description for any tag in this block
|
|
201
|
+
// Only the first tag (that needs a description) should use the comment block description
|
|
202
|
+
let commentDescriptionUsed = false;
|
|
203
|
+
let isFirstTag = true;
|
|
204
|
+
// Build a map of line numbers to their description content (for lines without tags)
|
|
205
|
+
const lineDescriptions = new Map();
|
|
206
|
+
// Track line numbers that contain tags
|
|
207
|
+
const tagLineNumbers = new Set();
|
|
208
|
+
for (const tagInfo of tags) {
|
|
209
|
+
if (tagInfo.source && tagInfo.source.length > 0) {
|
|
210
|
+
tagLineNumbers.add(tagInfo.source[0].number);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
for (const line of blockSource) {
|
|
214
|
+
// Only track lines that have a description but no tag
|
|
215
|
+
// Also filter out lines that are just "}" (artifact from some comment formats)
|
|
216
|
+
if (!line.tokens.tag && line.tokens.description && line.tokens.description.trim() !== "}") {
|
|
217
|
+
lineDescriptions.set(line.number, line.tokens.description);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
// Helper to get the description from lines preceding a tag
|
|
221
|
+
// Look backwards from the tag until we hit another tag, collecting description lines
|
|
222
|
+
// Stop after finding the first contiguous block of description lines
|
|
223
|
+
const getPrecedingDescription = (tagSource) => {
|
|
224
|
+
if (!tagSource || tagSource.length === 0)
|
|
225
|
+
return undefined;
|
|
226
|
+
const tagLineNumber = tagSource[0].number;
|
|
227
|
+
// Look backwards from the tag line to find the immediately preceding description
|
|
228
|
+
const descLines = [];
|
|
229
|
+
let foundDescriptionBlock = false;
|
|
230
|
+
for (let lineNum = tagLineNumber - 1; lineNum >= 1; lineNum--) {
|
|
231
|
+
// Stop if we hit a tag line
|
|
232
|
+
if (tagLineNumbers.has(lineNum)) {
|
|
233
|
+
break;
|
|
234
|
+
}
|
|
235
|
+
// Check if this line has a description
|
|
236
|
+
const desc = lineDescriptions.get(lineNum);
|
|
237
|
+
if (desc) {
|
|
238
|
+
descLines.unshift(desc); // Add to beginning to maintain order
|
|
239
|
+
foundDescriptionBlock = true;
|
|
240
|
+
}
|
|
241
|
+
else if (foundDescriptionBlock) {
|
|
242
|
+
// We've already found description lines and now hit a non-description line
|
|
243
|
+
// Check if it's blank - if so, continue; if not, stop
|
|
244
|
+
const sourceLine = blockSource.find((l) => l.number === lineNum);
|
|
245
|
+
const isBlank = !sourceLine ||
|
|
246
|
+
(!sourceLine.tokens.tag &&
|
|
247
|
+
(!sourceLine.tokens.description || sourceLine.tokens.description.trim() === ""));
|
|
248
|
+
if (!isBlank) {
|
|
249
|
+
// Non-blank non-description line - stop here
|
|
250
|
+
break;
|
|
251
|
+
}
|
|
252
|
+
// Blank line - continue (blank lines can separate descriptions from tags)
|
|
253
|
+
}
|
|
254
|
+
// If we haven't found any description yet, continue looking backwards
|
|
255
|
+
}
|
|
256
|
+
return descLines.length > 0 ? descLines.join("\n").trim() : undefined;
|
|
257
|
+
};
|
|
197
258
|
const finalizeEvent = () => {
|
|
198
259
|
if (currentEventName !== undefined) {
|
|
199
260
|
let detailType;
|
|
@@ -249,37 +310,63 @@ class ComponentParser {
|
|
|
249
310
|
currentTypedefDescription = undefined;
|
|
250
311
|
}
|
|
251
312
|
};
|
|
252
|
-
for (const { tag, type: tagType, name, description, optional, default: defaultValue } of tags) {
|
|
313
|
+
for (const { tag, type: tagType, name, description, optional, default: defaultValue, source: tagSource, } of tags) {
|
|
253
314
|
const type = this.aliasType(tagType);
|
|
315
|
+
// Get the description from the line immediately before this tag
|
|
316
|
+
const precedingDescription = getPrecedingDescription(tagSource);
|
|
254
317
|
switch (tag) {
|
|
255
318
|
case "extends":
|
|
256
319
|
this.extends = {
|
|
257
320
|
interface: name,
|
|
258
321
|
import: type,
|
|
259
322
|
};
|
|
323
|
+
if (isFirstTag)
|
|
324
|
+
isFirstTag = false;
|
|
260
325
|
break;
|
|
261
326
|
case "restProps":
|
|
262
327
|
this.rest_props = {
|
|
263
328
|
type: "Element",
|
|
264
329
|
name: type,
|
|
265
330
|
};
|
|
331
|
+
if (isFirstTag)
|
|
332
|
+
isFirstTag = false;
|
|
266
333
|
break;
|
|
267
|
-
case "slot":
|
|
334
|
+
case "slot": {
|
|
335
|
+
// Prefer inline description, fall back to preceding line description,
|
|
336
|
+
// then fall back to the comment block description (only for first tag if not already used)
|
|
337
|
+
const inlineSlotDesc = description?.replace(COMMENT_BLOCK_DESCRIPTION_REGEX, "").trim();
|
|
338
|
+
let slotDesc = inlineSlotDesc || precedingDescription;
|
|
339
|
+
if (!slotDesc && isFirstTag && !commentDescriptionUsed && commentDescription) {
|
|
340
|
+
slotDesc = commentDescription;
|
|
341
|
+
commentDescriptionUsed = true;
|
|
342
|
+
}
|
|
343
|
+
if (isFirstTag)
|
|
344
|
+
isFirstTag = false;
|
|
268
345
|
this.addSlot({
|
|
269
346
|
slot_name: name,
|
|
270
347
|
slot_props: type,
|
|
271
|
-
slot_description:
|
|
348
|
+
slot_description: slotDesc || undefined,
|
|
272
349
|
});
|
|
273
350
|
break;
|
|
274
|
-
|
|
351
|
+
}
|
|
352
|
+
case "event": {
|
|
275
353
|
// Finalize any previous event being built
|
|
276
354
|
finalizeEvent();
|
|
277
355
|
// Start tracking new event
|
|
278
356
|
currentEventName = name;
|
|
279
357
|
currentEventType = type;
|
|
280
|
-
//
|
|
281
|
-
|
|
358
|
+
// Prefer inline description (e.g., "@event {type} name - description"),
|
|
359
|
+
// fall back to preceding line, then fall back to comment block description (only for first tag if not already used)
|
|
360
|
+
const inlineEventDesc = description?.replace(COMMENT_BLOCK_DESCRIPTION_REGEX, "").trim();
|
|
361
|
+
currentEventDescription = inlineEventDesc || precedingDescription;
|
|
362
|
+
if (!currentEventDescription && isFirstTag && !commentDescriptionUsed && commentDescription) {
|
|
363
|
+
currentEventDescription = commentDescription;
|
|
364
|
+
commentDescriptionUsed = true;
|
|
365
|
+
}
|
|
366
|
+
if (isFirstTag)
|
|
367
|
+
isFirstTag = false;
|
|
282
368
|
break;
|
|
369
|
+
}
|
|
283
370
|
case "type":
|
|
284
371
|
// Track the @type tag for the current event
|
|
285
372
|
if (currentEventName !== undefined) {
|
|
@@ -309,14 +396,22 @@ class ComponentParser {
|
|
|
309
396
|
// Start tracking new typedef
|
|
310
397
|
currentTypedefName = name;
|
|
311
398
|
currentTypedefType = type;
|
|
312
|
-
//
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
399
|
+
// Prefer inline description, fall back to preceding line description,
|
|
400
|
+
// then fall back to comment block description (only for first tag if not already used)
|
|
401
|
+
const inlineTypedefDesc = description?.replace(COMMENT_BLOCK_DESCRIPTION_REGEX, "").trim();
|
|
402
|
+
currentTypedefDescription = inlineTypedefDesc || precedingDescription;
|
|
403
|
+
if (!currentTypedefDescription && isFirstTag && !commentDescriptionUsed && commentDescription) {
|
|
404
|
+
currentTypedefDescription = commentDescription;
|
|
405
|
+
commentDescriptionUsed = true;
|
|
406
|
+
}
|
|
407
|
+
if (isFirstTag)
|
|
408
|
+
isFirstTag = false;
|
|
316
409
|
break;
|
|
317
410
|
}
|
|
318
411
|
case "generics":
|
|
319
412
|
this.generics = [name, type];
|
|
413
|
+
if (isFirstTag)
|
|
414
|
+
isFirstTag = false;
|
|
320
415
|
break;
|
|
321
416
|
}
|
|
322
417
|
}
|
|
@@ -26,7 +26,14 @@ function formatTsProps(props) {
|
|
|
26
26
|
function getTypeDefs(def) {
|
|
27
27
|
if (def.typedefs.length === 0)
|
|
28
28
|
return EMPTY_STR;
|
|
29
|
-
return def.typedefs
|
|
29
|
+
return def.typedefs
|
|
30
|
+
.map((typedef) => {
|
|
31
|
+
const typedefComment = typedef.description
|
|
32
|
+
? `/**\n * ${typedef.description.replace(NEWLINE_TO_COMMENT_REGEX, "\n * ")}\n */\n`
|
|
33
|
+
: "";
|
|
34
|
+
return `${typedefComment}export ${typedef.ts}`;
|
|
35
|
+
})
|
|
36
|
+
.join("\n\n");
|
|
30
37
|
}
|
|
31
38
|
function getContextDefs(def) {
|
|
32
39
|
if (!def.contexts || def.contexts.length === 0)
|