sveld 0.26.2 → 0.27.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/README.md +4 -5
- package/cli.js +1 -7
- package/lib/index.js +184 -14
- package/package.json +10 -9
- package/lib/ComponentParser.js +0 -2657
- package/lib/cli.js +0 -48
- package/lib/create-exports.js +0 -170
- package/lib/element-tag-map.js +0 -158
- package/lib/get-svelte-entry.js +0 -39
- package/lib/parse-exports.js +0 -112
- package/lib/path.js +0 -12
- package/lib/plugin.js +0 -269
- package/lib/resolve-alias.js +0 -198
- package/lib/sveld.js +0 -33
- package/lib/writer/MarkdownWriterBase.js +0 -70
- package/lib/writer/Writer.js +0 -104
- package/lib/writer/WriterMarkdown.js +0 -65
- package/lib/writer/markdown-format-utils.js +0 -158
- package/lib/writer/markdown-render-utils.js +0 -89
- package/lib/writer/writer-json.js +0 -119
- package/lib/writer/writer-markdown-core.js +0 -44
- package/lib/writer/writer-markdown.js +0 -46
- package/lib/writer/writer-ts-definitions-core.js +0 -753
- package/lib/writer/writer-ts-definitions.js +0 -64
|
@@ -1,753 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.formatTsProps = formatTsProps;
|
|
4
|
-
exports.getTypeDefs = getTypeDefs;
|
|
5
|
-
exports.getContextDefs = getContextDefs;
|
|
6
|
-
exports.writeTsDefinition = writeTsDefinition;
|
|
7
|
-
const ANY_TYPE = "any";
|
|
8
|
-
const EMPTY_STR = "";
|
|
9
|
-
/**
|
|
10
|
-
* Empty events type definition.
|
|
11
|
-
*
|
|
12
|
-
* Svelte 4 is not compatible with `{}` type, so we use `Record<string, any>`
|
|
13
|
-
* instead for empty event objects.
|
|
14
|
-
*/
|
|
15
|
-
const EMPTY_EVENTS = "Record<string, any>";
|
|
16
|
-
/**
|
|
17
|
-
* Empty object type definition.
|
|
18
|
-
*
|
|
19
|
-
* Avoids `{}` type per Biome linter rule `noBannedTypes`.
|
|
20
|
-
* Uses `Record<string, never>` to represent an empty object type.
|
|
21
|
-
*/
|
|
22
|
-
const EMPTY_OBJECT = "Record<string, never>";
|
|
23
|
-
const CLAMP_KEY_REGEX = /(-|\s+|:)/;
|
|
24
|
-
const QUOTE_REGEX = /("|')/;
|
|
25
|
-
const CUSTOM_EVENT_REGEX = /CustomEvent/;
|
|
26
|
-
const COMPONENT_NAME_REGEX = /^[A-Z]/;
|
|
27
|
-
const NEWLINE_REGEX = /\n/;
|
|
28
|
-
const FUNCTION_TYPE_REGEX = /=>/;
|
|
29
|
-
const NEWLINE_TO_COMMENT_REGEX = /\n/g;
|
|
30
|
-
const WHITESPACE_REGEX = /\s+/g;
|
|
31
|
-
/**
|
|
32
|
-
* Formats a description for use in multi-line comment blocks.
|
|
33
|
-
* Replaces newlines with comment-formatted newlines.
|
|
34
|
-
*/
|
|
35
|
-
function formatDescriptionForComment(description) {
|
|
36
|
-
if (!description)
|
|
37
|
-
return undefined;
|
|
38
|
-
return description.replace(NEWLINE_TO_COMMENT_REGEX, "\n* ");
|
|
39
|
-
}
|
|
40
|
-
/**
|
|
41
|
-
* Formats a single-line JSDoc comment.
|
|
42
|
-
*/
|
|
43
|
-
function formatSingleLineComment(description) {
|
|
44
|
-
if (!description)
|
|
45
|
-
return "";
|
|
46
|
-
return `/** ${description} */`;
|
|
47
|
-
}
|
|
48
|
-
/**
|
|
49
|
-
* Formats a multi-line JSDoc comment with proper newline handling.
|
|
50
|
-
*/
|
|
51
|
-
function formatMultiLineComment(description) {
|
|
52
|
-
if (!description)
|
|
53
|
-
return "";
|
|
54
|
-
return `/**\n * ${description.replace(NEWLINE_TO_COMMENT_REGEX, "\n * ")}\n */`;
|
|
55
|
-
}
|
|
56
|
-
function formatTsProps(props) {
|
|
57
|
-
if (props === undefined)
|
|
58
|
-
return ANY_TYPE;
|
|
59
|
-
return `${props}\n`;
|
|
60
|
-
}
|
|
61
|
-
function getTypeDefs(def) {
|
|
62
|
-
if (def.typedefs.length === 0)
|
|
63
|
-
return EMPTY_STR;
|
|
64
|
-
return def.typedefs
|
|
65
|
-
.map((typedef) => {
|
|
66
|
-
const typedefComment = typedef.description ? `${formatMultiLineComment(typedef.description)}\n` : "";
|
|
67
|
-
return `${typedefComment}export ${typedef.ts}`;
|
|
68
|
-
})
|
|
69
|
-
.join("\n\n");
|
|
70
|
-
}
|
|
71
|
-
/**
|
|
72
|
-
* Generates TypeScript type definitions for component contexts.
|
|
73
|
-
*
|
|
74
|
-
* Creates exported type definitions for each context, including generic
|
|
75
|
-
* type parameters when contexts reference component generics. Handles
|
|
76
|
-
* empty context objects by using `Record<string, never>`.
|
|
77
|
-
*
|
|
78
|
-
* @param def - Component documentation containing contexts and generics
|
|
79
|
-
* @returns TypeScript type definition string, or empty string if no contexts
|
|
80
|
-
*
|
|
81
|
-
* @example
|
|
82
|
-
* ```ts
|
|
83
|
-
* // Input: contexts with generic reference
|
|
84
|
-
* // Output:
|
|
85
|
-
* // export type ModalContext<T> = {
|
|
86
|
-
* // /** Open the modal *\/
|
|
87
|
-
* // open: () => void;
|
|
88
|
-
* // /** Close the modal *\/
|
|
89
|
-
* // close: () => void;
|
|
90
|
-
* // };
|
|
91
|
-
* ```
|
|
92
|
-
*/
|
|
93
|
-
function getContextDefs(def) {
|
|
94
|
-
if (!def.contexts || def.contexts.length === 0)
|
|
95
|
-
return EMPTY_STR;
|
|
96
|
-
/**
|
|
97
|
-
* Extract generic parameter for context types if generics are defined
|
|
98
|
-
* and the context properties reference the generic type.
|
|
99
|
-
*/
|
|
100
|
-
const genericsName = def.generics ? def.generics[0] : null;
|
|
101
|
-
const genericsType = def.generics ? def.generics[1] : null;
|
|
102
|
-
return def.contexts
|
|
103
|
-
.map((context) => {
|
|
104
|
-
const props = context.properties
|
|
105
|
-
.map((prop) => {
|
|
106
|
-
const comment = prop.description ? `${formatSingleLineComment(prop.description)}\n ` : "";
|
|
107
|
-
const optional = prop.optional ? "?" : "";
|
|
108
|
-
return `${comment}${prop.name}${optional}: ${prop.type};`;
|
|
109
|
-
})
|
|
110
|
-
.join("\n ");
|
|
111
|
-
const contextComment = context.description ? `${formatMultiLineComment(context.description)}\n` : "";
|
|
112
|
-
// If context properties reference the generic type, parameterize the context type
|
|
113
|
-
const referencesGeneric = genericsName && context.properties.some((prop) => prop.type.includes(genericsName));
|
|
114
|
-
// Build generic suffix if context references generics (e.g., `ModalContext<T>`)
|
|
115
|
-
const genericSuffix = referencesGeneric && genericsType ? `<${genericsType}>` : "";
|
|
116
|
-
/**
|
|
117
|
-
* Use Record<string, never> for empty context objects instead of {}.
|
|
118
|
-
* This complies with Biome linter rules and Svelte 4 compatibility.
|
|
119
|
-
*/
|
|
120
|
-
if (context.properties.length === 0) {
|
|
121
|
-
return `${contextComment}export type ${context.typeName} = Record<string, never>;`;
|
|
122
|
-
}
|
|
123
|
-
return `${contextComment}export type ${context.typeName}${genericSuffix} = {\n ${props}\n};`;
|
|
124
|
-
})
|
|
125
|
-
.join("\n\n");
|
|
126
|
-
}
|
|
127
|
-
function clampKey(key) {
|
|
128
|
-
if (CLAMP_KEY_REGEX.test(key)) {
|
|
129
|
-
return QUOTE_REGEX.test(key) ? key : `"${key}"`;
|
|
130
|
-
}
|
|
131
|
-
return key;
|
|
132
|
-
}
|
|
133
|
-
function addCommentLine(value, returnValue) {
|
|
134
|
-
if (!value)
|
|
135
|
-
return undefined;
|
|
136
|
-
return `* ${returnValue || value}\n`;
|
|
137
|
-
}
|
|
138
|
-
/**
|
|
139
|
-
* Creates a prop comment string from a description.
|
|
140
|
-
*/
|
|
141
|
-
function createPropComment(description) {
|
|
142
|
-
return [addCommentLine(formatDescriptionForComment(description))].filter(Boolean).join("");
|
|
143
|
-
}
|
|
144
|
-
/**
|
|
145
|
-
* Wraps comment lines in JSDoc format if comments exist.
|
|
146
|
-
*/
|
|
147
|
-
function wrapCommentInJSDoc(commentLines) {
|
|
148
|
-
return commentLines.length > 0 ? `/**\n${commentLines}*/` : EMPTY_STR;
|
|
149
|
-
}
|
|
150
|
-
/**
|
|
151
|
-
* Generates TypeScript prop definitions for a component.
|
|
152
|
-
*
|
|
153
|
-
* Creates the `$Props` type definition including:
|
|
154
|
-
* - Regular props from component declarations
|
|
155
|
-
* - Snippet props for slots (Svelte 5 compatibility)
|
|
156
|
-
* - Rest props handling for elements/components
|
|
157
|
-
* - Generic type parameters
|
|
158
|
-
* - Extends interface merging
|
|
159
|
-
*
|
|
160
|
-
* @param def - Component documentation containing props, slots, rest_props, etc.
|
|
161
|
-
* @returns An object with the props type name and the generated type definition
|
|
162
|
-
*
|
|
163
|
-
* @example
|
|
164
|
-
* ```ts
|
|
165
|
-
* // Generates:
|
|
166
|
-
* // type $Props<T extends string = "default"> = {
|
|
167
|
-
* // /** The count value *\/
|
|
168
|
-
* // count?: number;
|
|
169
|
-
* // /** Header slot *\/
|
|
170
|
-
* // header?: (this: void, ...args: [{ title: string }]) => void;
|
|
171
|
-
* // children?: (this: void) => void;
|
|
172
|
-
* // };
|
|
173
|
-
* ```
|
|
174
|
-
*/
|
|
175
|
-
function genPropDef(def) {
|
|
176
|
-
/**
|
|
177
|
-
* Collect existing prop names to avoid conflicts with snippet props.
|
|
178
|
-
* Snippet props are generated for slots, but shouldn't conflict with
|
|
179
|
-
* actual component props.
|
|
180
|
-
*/
|
|
181
|
-
const existingPropNames = new Set(def.props.filter((prop) => !prop.isFunctionDeclaration && prop.kind !== "const").map((prop) => prop.name));
|
|
182
|
-
const initial_props = def.props
|
|
183
|
-
.filter((prop) => !prop.isFunctionDeclaration && prop.kind !== "const")
|
|
184
|
-
.map((prop) => {
|
|
185
|
-
let defaultValue = prop.value;
|
|
186
|
-
if (typeof prop.value === "string") {
|
|
187
|
-
defaultValue = prop.value.replace(WHITESPACE_REGEX, " ");
|
|
188
|
-
}
|
|
189
|
-
if (prop.value === undefined) {
|
|
190
|
-
defaultValue = "undefined";
|
|
191
|
-
}
|
|
192
|
-
const prop_comments = [
|
|
193
|
-
addCommentLine(formatDescriptionForComment(prop.description)),
|
|
194
|
-
addCommentLine(prop.constant, "@constant"),
|
|
195
|
-
/**
|
|
196
|
-
* Don't add @default for functions - they don't have meaningful default values.
|
|
197
|
-
* Function props are callbacks, not values with defaults.
|
|
198
|
-
*/
|
|
199
|
-
prop.isFunction ? null : `* @default ${defaultValue}\n`,
|
|
200
|
-
]
|
|
201
|
-
.filter(Boolean)
|
|
202
|
-
.join("");
|
|
203
|
-
const prop_value = prop.constant && !prop.isFunction ? prop.value : prop.type;
|
|
204
|
-
return `
|
|
205
|
-
${wrapCommentInJSDoc(prop_comments)}
|
|
206
|
-
${prop.name}${prop.isRequired ? "" : "?"}: ${prop_value};`;
|
|
207
|
-
});
|
|
208
|
-
/**
|
|
209
|
-
* Generate snippet props for named slots (Svelte 5 compatibility).
|
|
210
|
-
* Svelte 5 uses snippet props to type-check slot content. Skip default slots
|
|
211
|
-
* and slots that conflict with existing prop names to avoid type conflicts.
|
|
212
|
-
*/
|
|
213
|
-
const named_snippet_props = (def.slots || [])
|
|
214
|
-
.filter((slot) => !slot.default && slot.name != null && !existingPropNames.has(slot.name))
|
|
215
|
-
.map((slot) => {
|
|
216
|
-
const slotName = slot.name;
|
|
217
|
-
const key = clampKey(slotName);
|
|
218
|
-
const description = slot.description ? `${formatSingleLineComment(slot.description)}\n ` : "";
|
|
219
|
-
/**
|
|
220
|
-
* Use Snippet-compatible type: (this: void, ...args: [Props]) => void for slots with props
|
|
221
|
-
* or (this: void) => void for slots without props.
|
|
222
|
-
* The `this: void` parameter ensures the snippet function cannot access `this`.
|
|
223
|
-
*/
|
|
224
|
-
const hasSlotProps = slot.slot_props && slot.slot_props !== "Record<string, never>";
|
|
225
|
-
const snippetType = hasSlotProps ? `(this: void, ...args: [${slot.slot_props}]) => void` : "(this: void) => void";
|
|
226
|
-
return `
|
|
227
|
-
${description}${key}?: ${snippetType};`;
|
|
228
|
-
});
|
|
229
|
-
/**
|
|
230
|
-
* Generate children snippet prop for default slot (Svelte 5 compatibility).
|
|
231
|
-
* The default slot is accessed via the `children` prop in Svelte 5's snippet API.
|
|
232
|
-
*/
|
|
233
|
-
const default_slot = (def.slots || []).find((slot) => slot.default || slot.name === null);
|
|
234
|
-
const children_snippet_prop = default_slot
|
|
235
|
-
? (() => {
|
|
236
|
-
const description = default_slot.description
|
|
237
|
-
? `${formatSingleLineComment(default_slot.description)}\n `
|
|
238
|
-
: "";
|
|
239
|
-
const hasSlotProps = default_slot.slot_props && default_slot.slot_props !== "Record<string, never>";
|
|
240
|
-
const snippetType = hasSlotProps
|
|
241
|
-
? `(this: void, ...args: [${default_slot.slot_props}]) => void`
|
|
242
|
-
: "(this: void) => void";
|
|
243
|
-
return `
|
|
244
|
-
${description}children?: ${snippetType};`;
|
|
245
|
-
})()
|
|
246
|
-
: "";
|
|
247
|
-
const snippet_props = [...named_snippet_props, children_snippet_prop].filter(Boolean);
|
|
248
|
-
const props = [...initial_props, ...snippet_props].join("\n");
|
|
249
|
-
const props_name = `${def.moduleName}Props`;
|
|
250
|
-
let prop_def = EMPTY_STR;
|
|
251
|
-
/**
|
|
252
|
-
* Full constraints for type definitions (e.g., `type $Props<T extends Foo = Bar>`).
|
|
253
|
-
* Includes the full generic constraint with extends and default.
|
|
254
|
-
*/
|
|
255
|
-
const genericsName = def.generics ? `<${def.generics[1]}>` : "";
|
|
256
|
-
/**
|
|
257
|
-
* Names only for type references (e.g., `keyof $Props<T>`).
|
|
258
|
-
* Just the generic parameter name without constraints.
|
|
259
|
-
*/
|
|
260
|
-
const genericsNameRef = def.generics ? `<${def.generics[0]}>` : "";
|
|
261
|
-
if (def.rest_props?.type === "Element") {
|
|
262
|
-
let extend_tag_map;
|
|
263
|
-
/**
|
|
264
|
-
* Handle svelte:element specially.
|
|
265
|
-
* svelte:element can have either a static tag (thisValue) or dynamic tag.
|
|
266
|
-
*/
|
|
267
|
-
if (def.rest_props.name === "svelte:element") {
|
|
268
|
-
/**
|
|
269
|
-
* If thisValue is provided (hardcoded element tag), use that element type.
|
|
270
|
-
* Otherwise, fallback to HTMLElement for dynamic this attribute.
|
|
271
|
-
*/
|
|
272
|
-
if (def.rest_props.thisValue) {
|
|
273
|
-
extend_tag_map = `SvelteHTMLElements["${def.rest_props.thisValue}"]`;
|
|
274
|
-
}
|
|
275
|
-
else {
|
|
276
|
-
/**
|
|
277
|
-
* Dynamic this attribute - use generic HTMLElement.
|
|
278
|
-
* Since we don't know the element type at compile time, use the base type.
|
|
279
|
-
*/
|
|
280
|
-
extend_tag_map = "HTMLAttributes<HTMLElement>";
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
else {
|
|
284
|
-
// Map element names to their SvelteHTMLElements types
|
|
285
|
-
extend_tag_map = def.rest_props.name
|
|
286
|
-
.split("|")
|
|
287
|
-
.map((name) => {
|
|
288
|
-
const element = name.trim();
|
|
289
|
-
return `SvelteHTMLElements["${element}"]`;
|
|
290
|
-
})
|
|
291
|
-
.join("&");
|
|
292
|
-
}
|
|
293
|
-
/**
|
|
294
|
-
* Components that extend HTML elements should allow for `data-*` attributes.
|
|
295
|
-
* @see https://github.com/sveltejs/language-tools/issues/1825
|
|
296
|
-
*
|
|
297
|
-
* Even though Svelte 4 does this automatically, we need to preserve this for Svelte 3.
|
|
298
|
-
*/
|
|
299
|
-
/**
|
|
300
|
-
* biome-ignore lint/suspicious/noTemplateCurlyInString: type generation
|
|
301
|
-
* Template literal is required for TypeScript's template literal type syntax.
|
|
302
|
-
*/
|
|
303
|
-
const dataAttributes = "[key: `data-${string}`]: any;";
|
|
304
|
-
/**
|
|
305
|
-
* Generate JSDoc comment for $RestProps if description is provided.
|
|
306
|
-
* Use multiline format when description contains newlines.
|
|
307
|
-
*/
|
|
308
|
-
const restPropsComment = def.rest_props.description
|
|
309
|
-
? def.rest_props.description.includes("\n")
|
|
310
|
-
? `${formatMultiLineComment(def.rest_props.description)}\n `
|
|
311
|
-
: `${formatSingleLineComment(def.rest_props.description)}\n `
|
|
312
|
-
: "";
|
|
313
|
-
/**
|
|
314
|
-
* When both `@extends` and `@restProps` are present, merge all three type sources:
|
|
315
|
-
* 1. Rest props from element types (SvelteHTMLElements)
|
|
316
|
-
* 2. Component props ($Props)
|
|
317
|
-
* 3. Extended interface (`@extends`)
|
|
318
|
-
*/
|
|
319
|
-
if (def.extends !== undefined) {
|
|
320
|
-
prop_def = `
|
|
321
|
-
${restPropsComment}${extend_tag_map ? `type $RestProps = ${extend_tag_map};\n` : ""}
|
|
322
|
-
type $Props${genericsName} = {
|
|
323
|
-
${props}
|
|
324
|
-
|
|
325
|
-
${dataAttributes}
|
|
326
|
-
};
|
|
327
|
-
|
|
328
|
-
export type ${props_name}${genericsName} = Omit<$RestProps, keyof ($Props${genericsNameRef} & ${def.extends.interface})> & $Props${genericsNameRef} & ${def.extends.interface};
|
|
329
|
-
`;
|
|
330
|
-
}
|
|
331
|
-
else {
|
|
332
|
-
prop_def = `
|
|
333
|
-
${restPropsComment}${extend_tag_map ? `type $RestProps = ${extend_tag_map};\n` : ""}
|
|
334
|
-
type $Props${genericsName} = {
|
|
335
|
-
${props}
|
|
336
|
-
|
|
337
|
-
${dataAttributes}
|
|
338
|
-
};
|
|
339
|
-
|
|
340
|
-
export type ${props_name}${genericsName} = Omit<$RestProps, keyof $Props${genericsNameRef}> & $Props${genericsNameRef};
|
|
341
|
-
`;
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
else {
|
|
345
|
-
/**
|
|
346
|
-
* Use EMPTY_OBJECT when there are no props and no extends.
|
|
347
|
-
* This ensures we don't generate `{}` which is incompatible with Svelte 4
|
|
348
|
-
* and violates Biome linter rules.
|
|
349
|
-
*/
|
|
350
|
-
if (props.trim() === "" && def.extends === undefined) {
|
|
351
|
-
prop_def = `
|
|
352
|
-
export type ${props_name}${genericsName} = ${EMPTY_OBJECT};
|
|
353
|
-
`;
|
|
354
|
-
}
|
|
355
|
-
else {
|
|
356
|
-
prop_def = `
|
|
357
|
-
export type ${props_name}${genericsName} = ${def.extends !== undefined ? `${def.extends.interface} & ` : ""} {
|
|
358
|
-
${props}
|
|
359
|
-
}
|
|
360
|
-
`;
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
return {
|
|
364
|
-
props_name,
|
|
365
|
-
prop_def,
|
|
366
|
-
};
|
|
367
|
-
}
|
|
368
|
-
function genSlotDef(def) {
|
|
369
|
-
if (def.slots.length === 0)
|
|
370
|
-
return EMPTY_OBJECT;
|
|
371
|
-
const slotDefs = def.slots
|
|
372
|
-
.map(({ name, slot_props, ...rest }) => {
|
|
373
|
-
const key = rest.default || name === null ? "default" : clampKey(name ?? "");
|
|
374
|
-
const description = rest.description ? `${formatSingleLineComment(rest.description)}\n` : "";
|
|
375
|
-
return `${description}${clampKey(key)}: ${formatTsProps(slot_props)};`;
|
|
376
|
-
})
|
|
377
|
-
.join("\n");
|
|
378
|
-
return `{${slotDefs}}`;
|
|
379
|
-
}
|
|
380
|
-
/**
|
|
381
|
-
* Returns the type name for mapping standard DOM events.
|
|
382
|
-
*
|
|
383
|
-
* lib.dom.d.ts should map event types by name using WindowEventMap.
|
|
384
|
-
* This function returns the type name for that mapping.
|
|
385
|
-
*
|
|
386
|
-
* @returns The type name "WindowEventMap"
|
|
387
|
-
*/
|
|
388
|
-
const mapEvent = () => {
|
|
389
|
-
return "WindowEventMap";
|
|
390
|
-
};
|
|
391
|
-
/**
|
|
392
|
-
* Standard DOM events that should use WindowEventMap for type inference.
|
|
393
|
-
*
|
|
394
|
-
* These events are standard browser events that have well-defined types
|
|
395
|
-
* in the DOM type definitions. Using WindowEventMap provides better
|
|
396
|
-
* type safety than generic CustomEvent types.
|
|
397
|
-
*/
|
|
398
|
-
const STANDARD_DOM_EVENTS = new Set([
|
|
399
|
-
/**
|
|
400
|
-
* Mouse events - pointer interactions with the mouse.
|
|
401
|
-
*/
|
|
402
|
-
"click",
|
|
403
|
-
"dblclick",
|
|
404
|
-
"mousedown",
|
|
405
|
-
"mouseup",
|
|
406
|
-
"mousemove",
|
|
407
|
-
"mouseover",
|
|
408
|
-
"mouseout",
|
|
409
|
-
"mouseenter",
|
|
410
|
-
"mouseleave",
|
|
411
|
-
"contextmenu",
|
|
412
|
-
"wheel",
|
|
413
|
-
/**
|
|
414
|
-
* Keyboard events - key press interactions.
|
|
415
|
-
*/
|
|
416
|
-
"keydown",
|
|
417
|
-
"keyup",
|
|
418
|
-
"keypress",
|
|
419
|
-
/**
|
|
420
|
-
* Form events - form element interactions.
|
|
421
|
-
*/
|
|
422
|
-
"submit",
|
|
423
|
-
"change",
|
|
424
|
-
"input",
|
|
425
|
-
"focus",
|
|
426
|
-
"blur",
|
|
427
|
-
"focusin",
|
|
428
|
-
"focusout",
|
|
429
|
-
"reset",
|
|
430
|
-
"select",
|
|
431
|
-
/**
|
|
432
|
-
* Touch events - touch screen interactions.
|
|
433
|
-
*/
|
|
434
|
-
"touchstart",
|
|
435
|
-
"touchend",
|
|
436
|
-
"touchmove",
|
|
437
|
-
"touchcancel",
|
|
438
|
-
/**
|
|
439
|
-
* Drag events - drag and drop interactions.
|
|
440
|
-
*/
|
|
441
|
-
"drag",
|
|
442
|
-
"dragstart",
|
|
443
|
-
"dragend",
|
|
444
|
-
"dragover",
|
|
445
|
-
"dragenter",
|
|
446
|
-
"dragleave",
|
|
447
|
-
"drop",
|
|
448
|
-
/**
|
|
449
|
-
* Pointer events - unified pointer interactions (mouse, touch, pen).
|
|
450
|
-
*/
|
|
451
|
-
"pointerdown",
|
|
452
|
-
"pointerup",
|
|
453
|
-
"pointermove",
|
|
454
|
-
"pointerover",
|
|
455
|
-
"pointerout",
|
|
456
|
-
"pointerenter",
|
|
457
|
-
"pointerleave",
|
|
458
|
-
"pointercancel",
|
|
459
|
-
"gotpointercapture",
|
|
460
|
-
"lostpointercapture",
|
|
461
|
-
/**
|
|
462
|
-
* Media events - audio/video element events.
|
|
463
|
-
*/
|
|
464
|
-
"play",
|
|
465
|
-
"pause",
|
|
466
|
-
"ended",
|
|
467
|
-
"volumechange",
|
|
468
|
-
"timeupdate",
|
|
469
|
-
"loadeddata",
|
|
470
|
-
"loadedmetadata",
|
|
471
|
-
"canplay",
|
|
472
|
-
"canplaythrough",
|
|
473
|
-
"seeking",
|
|
474
|
-
"seeked",
|
|
475
|
-
"playing",
|
|
476
|
-
"waiting",
|
|
477
|
-
"stalled",
|
|
478
|
-
"suspend",
|
|
479
|
-
"abort",
|
|
480
|
-
"error",
|
|
481
|
-
"emptied",
|
|
482
|
-
"ratechange",
|
|
483
|
-
"durationchange",
|
|
484
|
-
"loadstart",
|
|
485
|
-
"progress",
|
|
486
|
-
"loadend",
|
|
487
|
-
/**
|
|
488
|
-
* Animation/Transition events - CSS animation and transition events.
|
|
489
|
-
*/
|
|
490
|
-
"animationstart",
|
|
491
|
-
"animationend",
|
|
492
|
-
"animationiteration",
|
|
493
|
-
"animationcancel",
|
|
494
|
-
"transitionstart",
|
|
495
|
-
"transitionend",
|
|
496
|
-
"transitionrun",
|
|
497
|
-
"transitioncancel",
|
|
498
|
-
/**
|
|
499
|
-
* Other events - miscellaneous browser events.
|
|
500
|
-
*/
|
|
501
|
-
"scroll",
|
|
502
|
-
"resize",
|
|
503
|
-
"load",
|
|
504
|
-
"unload",
|
|
505
|
-
"beforeunload",
|
|
506
|
-
"cut",
|
|
507
|
-
"copy",
|
|
508
|
-
"paste",
|
|
509
|
-
"compositionstart",
|
|
510
|
-
"compositionupdate",
|
|
511
|
-
"compositionend",
|
|
512
|
-
]);
|
|
513
|
-
function genEventDef(def) {
|
|
514
|
-
const createDispatchedEvent = (detail = ANY_TYPE) => {
|
|
515
|
-
if (CUSTOM_EVENT_REGEX.test(detail))
|
|
516
|
-
return detail;
|
|
517
|
-
return `CustomEvent<${detail}>`;
|
|
518
|
-
};
|
|
519
|
-
/**
|
|
520
|
-
* Check if an event name is a standard DOM event that exists in WindowEventMap.
|
|
521
|
-
* Standard DOM events should use WindowEventMap for better type inference.
|
|
522
|
-
*/
|
|
523
|
-
const isStandardDomEvent = (eventName) => {
|
|
524
|
-
return STANDARD_DOM_EVENTS.has(eventName);
|
|
525
|
-
};
|
|
526
|
-
if (def.events.length === 0)
|
|
527
|
-
return EMPTY_EVENTS;
|
|
528
|
-
const events_map = def.events
|
|
529
|
-
.map((event) => {
|
|
530
|
-
let description = "";
|
|
531
|
-
if (event.description) {
|
|
532
|
-
description = `${formatSingleLineComment(event.description)}\n`;
|
|
533
|
-
}
|
|
534
|
-
let eventType;
|
|
535
|
-
if (event.type === "dispatched") {
|
|
536
|
-
eventType = createDispatchedEvent(event.detail);
|
|
537
|
-
}
|
|
538
|
-
else {
|
|
539
|
-
/**
|
|
540
|
-
* For forwarded events, determine the type based on `@event` JSDoc and element/event type.
|
|
541
|
-
* Handle both serialized (string) and object formats for backward compatibility.
|
|
542
|
-
*/
|
|
543
|
-
const elementName = typeof event.element === "string" ? event.element : event.element.name;
|
|
544
|
-
const isComponent = elementName && COMPONENT_NAME_REGEX.test(elementName);
|
|
545
|
-
const isStandardEvent = !isComponent || isStandardDomEvent(event.name);
|
|
546
|
-
/**
|
|
547
|
-
* Check if there's an explicit detail type from `@event` JSDoc (including null).
|
|
548
|
-
* Note: detail="null" on standard DOM events is treated as "not explicitly typed"
|
|
549
|
-
* because `@event` click (without {type}) defaults to null but shouldn't override WindowEventMap.
|
|
550
|
-
* However, for custom component events, explicit null should be respected.
|
|
551
|
-
*/
|
|
552
|
-
const hasExplicitDetail = event.detail !== undefined && event.detail !== "undefined" && !(event.detail === "null" && isStandardEvent);
|
|
553
|
-
const hasExplicitNullForCustomComponent = event.detail === "null" && !isStandardEvent;
|
|
554
|
-
if (hasExplicitDetail || hasExplicitNullForCustomComponent) {
|
|
555
|
-
/**
|
|
556
|
-
* If `@event` tag explicitly provides a detail type (including null for custom components),
|
|
557
|
-
* always use it (highest priority). This allows developers to override default behavior.
|
|
558
|
-
*/
|
|
559
|
-
eventType = createDispatchedEvent(event.detail);
|
|
560
|
-
}
|
|
561
|
-
else if (isStandardEvent) {
|
|
562
|
-
/**
|
|
563
|
-
* Standard DOM event (native element or standard event name) without explicit type.
|
|
564
|
-
* Use WindowEventMap for better type inference from lib.dom.d.ts.
|
|
565
|
-
*/
|
|
566
|
-
eventType = `${mapEvent()}["${event.name}"]`;
|
|
567
|
-
}
|
|
568
|
-
else {
|
|
569
|
-
/**
|
|
570
|
-
* Custom event from component with no explicit type.
|
|
571
|
-
* Default to CustomEvent<any> since we don't know the detail type.
|
|
572
|
-
*/
|
|
573
|
-
eventType = createDispatchedEvent();
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
return `${description}${clampKey(event.name)}: ${eventType};\n`;
|
|
577
|
-
})
|
|
578
|
-
.join("");
|
|
579
|
-
return `{${events_map}}`;
|
|
580
|
-
}
|
|
581
|
-
/**
|
|
582
|
-
* Generates a function type string from a prop's type, params, and returnType.
|
|
583
|
-
* Priority: `@type` tag > `@param`/`@returns` tags > fallback to prop.type
|
|
584
|
-
*/
|
|
585
|
-
function generateFunctionType(prop) {
|
|
586
|
-
/**
|
|
587
|
-
* Check if this is the default function type (would be overridden by `@type` or params/returns).
|
|
588
|
-
* The default `() => any` type is a placeholder that should be replaced with more specific types.
|
|
589
|
-
*/
|
|
590
|
-
const isDefaultFunctionType = prop.type === "() => any";
|
|
591
|
-
/**
|
|
592
|
-
* If `@type` tag provides a custom function signature (contains => and is not the default),
|
|
593
|
-
* use it (highest priority). This allows explicit function type annotations.
|
|
594
|
-
*/
|
|
595
|
-
if (prop.type && FUNCTION_TYPE_REGEX.test(prop.type) && !isDefaultFunctionType) {
|
|
596
|
-
return prop.type;
|
|
597
|
-
}
|
|
598
|
-
else if (prop.params && prop.params.length > 0) {
|
|
599
|
-
// Build signature from `@param` tags (most detailed from JSDoc annotations)
|
|
600
|
-
const paramStrings = prop.params.map((param) => {
|
|
601
|
-
const optional = param.optional ? "?" : "";
|
|
602
|
-
return `${param.name}${optional}: ${param.type}`;
|
|
603
|
-
});
|
|
604
|
-
const paramsString = paramStrings.join(", ");
|
|
605
|
-
const returnType = prop.returnType || ANY_TYPE;
|
|
606
|
-
return `(${paramsString}) => ${returnType}`;
|
|
607
|
-
}
|
|
608
|
-
else if (prop.returnType) {
|
|
609
|
-
// Only `@returns` is present without `@param`
|
|
610
|
-
return `() => ${prop.returnType}`;
|
|
611
|
-
}
|
|
612
|
-
else {
|
|
613
|
-
/**
|
|
614
|
-
* Fall back to current prop.type.
|
|
615
|
-
* If no JSDoc annotations are present, use the inferred type.
|
|
616
|
-
*/
|
|
617
|
-
return prop.type || ANY_TYPE;
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
function genAccessors(def) {
|
|
621
|
-
return def.props
|
|
622
|
-
.filter((prop) => prop.isFunctionDeclaration || prop.kind === "const")
|
|
623
|
-
.map((prop) => {
|
|
624
|
-
const prop_comments = createPropComment(prop.description);
|
|
625
|
-
const functionType = generateFunctionType(prop);
|
|
626
|
-
return `
|
|
627
|
-
${wrapCommentInJSDoc(prop_comments)}
|
|
628
|
-
${prop.name}: ${functionType};`;
|
|
629
|
-
})
|
|
630
|
-
.join("\n");
|
|
631
|
-
}
|
|
632
|
-
function genImports(def) {
|
|
633
|
-
if (def.extends === undefined)
|
|
634
|
-
return "";
|
|
635
|
-
return `import type { ${def.extends.interface} } from ${def.extends.import};`;
|
|
636
|
-
}
|
|
637
|
-
function genComponentComment(def) {
|
|
638
|
-
if (!def.componentComment)
|
|
639
|
-
return "";
|
|
640
|
-
if (!NEWLINE_REGEX.test(def.componentComment)) {
|
|
641
|
-
return formatSingleLineComment(def.componentComment.trim());
|
|
642
|
-
}
|
|
643
|
-
return `/*${def.componentComment
|
|
644
|
-
.split("\n")
|
|
645
|
-
.map((line) => `* ${line}`)
|
|
646
|
-
.join("\n")}\n*/`;
|
|
647
|
-
}
|
|
648
|
-
function genModuleExports(def) {
|
|
649
|
-
return def.moduleExports
|
|
650
|
-
.map((prop) => {
|
|
651
|
-
const prop_comments = createPropComment(prop.description);
|
|
652
|
-
let type_def;
|
|
653
|
-
const is_function = prop.type && FUNCTION_TYPE_REGEX.test(prop.type);
|
|
654
|
-
const isDefaultFunctionType = prop.type === "() => any";
|
|
655
|
-
/**
|
|
656
|
-
* Check for const exports first (but only if not a function).
|
|
657
|
-
* Const exports from script context="module" should use `declare const`.
|
|
658
|
-
*/
|
|
659
|
-
if (prop.kind === "const" && !is_function) {
|
|
660
|
-
/**
|
|
661
|
-
* For const exports from script context="module", use declare const instead of type.
|
|
662
|
-
* This matches how TypeScript handles const declarations in .d.ts files.
|
|
663
|
-
*/
|
|
664
|
-
type_def = `export declare const ${prop.name}: ${prop.type || ANY_TYPE};\n`;
|
|
665
|
-
}
|
|
666
|
-
else if (prop.params && prop.params.length > 0) {
|
|
667
|
-
// Build signature from `@param` tags (highest priority for functions)
|
|
668
|
-
const paramStrings = prop.params.map((param) => {
|
|
669
|
-
const optional = param.optional ? "?" : "";
|
|
670
|
-
return `${param.name}${optional}: ${param.type}`;
|
|
671
|
-
});
|
|
672
|
-
const paramsString = paramStrings.join(", ");
|
|
673
|
-
const returnType = prop.returnType || ANY_TYPE;
|
|
674
|
-
type_def = `export declare function ${prop.name}(${paramsString}): ${returnType};`;
|
|
675
|
-
}
|
|
676
|
-
else if (prop.returnType) {
|
|
677
|
-
// Only `@returns` is present without `@param`
|
|
678
|
-
type_def = `export declare function ${prop.name}(): ${prop.returnType};`;
|
|
679
|
-
}
|
|
680
|
-
else if (is_function && prop.type && !isDefaultFunctionType) {
|
|
681
|
-
/**
|
|
682
|
-
* `@type` tag provides a custom function signature.
|
|
683
|
-
* Convert function type to function declaration format.
|
|
684
|
-
*/
|
|
685
|
-
const [first, second, ...rest] = prop.type.split("=>");
|
|
686
|
-
const rest_type = rest.map((item) => `=>${item}`).join("");
|
|
687
|
-
type_def = `export declare function ${prop.name}${first}:${second}${rest_type};`;
|
|
688
|
-
}
|
|
689
|
-
else if (is_function && prop.type) {
|
|
690
|
-
/**
|
|
691
|
-
* Fall back to existing function type handling (including default function type).
|
|
692
|
-
* Convert the function type expression to a function declaration.
|
|
693
|
-
*/
|
|
694
|
-
const [first, second, ...rest] = prop.type.split("=>");
|
|
695
|
-
const rest_type = rest.map((item) => `=>${item}`).join("");
|
|
696
|
-
type_def = `export declare function ${prop.name}${first}:${second}${rest_type};`;
|
|
697
|
-
}
|
|
698
|
-
else if (prop.kind === "const") {
|
|
699
|
-
/**
|
|
700
|
-
* Const exports that are functions (shouldn't happen, but handle gracefully).
|
|
701
|
-
* Treat as const with function type.
|
|
702
|
-
*/
|
|
703
|
-
type_def = `export declare const ${prop.name}: ${prop.type || ANY_TYPE};\n`;
|
|
704
|
-
}
|
|
705
|
-
else {
|
|
706
|
-
/**
|
|
707
|
-
* Default: export as type.
|
|
708
|
-
* For non-function, non-const exports, use type alias.
|
|
709
|
-
*/
|
|
710
|
-
type_def = `export type ${prop.name} = ${prop.type || ANY_TYPE};`;
|
|
711
|
-
}
|
|
712
|
-
return `
|
|
713
|
-
${wrapCommentInJSDoc(prop_comments)}
|
|
714
|
-
${type_def}`;
|
|
715
|
-
})
|
|
716
|
-
.join("\n");
|
|
717
|
-
}
|
|
718
|
-
function writeTsDefinition(component) {
|
|
719
|
-
const { moduleName, typedefs, generics, props, moduleExports, slots, events, rest_props, extends: _extends, componentComment, contexts, } = component;
|
|
720
|
-
const { props_name, prop_def } = genPropDef({
|
|
721
|
-
moduleName,
|
|
722
|
-
props,
|
|
723
|
-
rest_props,
|
|
724
|
-
extends: _extends,
|
|
725
|
-
generics,
|
|
726
|
-
slots,
|
|
727
|
-
});
|
|
728
|
-
const generic = generics ? `<${generics[1]}>` : "";
|
|
729
|
-
const genericProps = generics ? `${props_name}<${generics[0]}>` : props_name;
|
|
730
|
-
/**
|
|
731
|
-
* Determine imports needed for rest_props.
|
|
732
|
-
* SvelteHTMLElements is needed for regular elements and svelte:element with static tags.
|
|
733
|
-
* HTMLAttributes is needed for dynamic svelte:element (no thisValue).
|
|
734
|
-
*/
|
|
735
|
-
const needsSvelteHTMLElements = rest_props?.type === "Element" &&
|
|
736
|
-
(rest_props.name !== "svelte:element" || (rest_props.name === "svelte:element" && rest_props.thisValue));
|
|
737
|
-
const needsHTMLAttributes = rest_props?.type === "Element" && rest_props.name === "svelte:element" && !rest_props.thisValue;
|
|
738
|
-
return `
|
|
739
|
-
import { SvelteComponentTyped } from "svelte";${needsSvelteHTMLElements ? `import type { SvelteHTMLElements } from "svelte/elements";\n` : ""}${needsHTMLAttributes ? `import type { HTMLAttributes } from "svelte/elements";\n` : ""}
|
|
740
|
-
${genImports({ extends: _extends })}
|
|
741
|
-
${genModuleExports({ moduleExports })}
|
|
742
|
-
${getTypeDefs({ typedefs })}
|
|
743
|
-
${contexts && contexts.length > 0 ? "\n" : ""}${getContextDefs({ contexts, generics })}
|
|
744
|
-
${prop_def}
|
|
745
|
-
${genComponentComment({ componentComment })}
|
|
746
|
-
export default class ${moduleName === "default" ? "" : moduleName}${generic} extends SvelteComponentTyped<
|
|
747
|
-
${genericProps},
|
|
748
|
-
${genEventDef({ events })},
|
|
749
|
-
${genSlotDef({ slots })}
|
|
750
|
-
> {
|
|
751
|
-
${genAccessors({ props })}
|
|
752
|
-
}`;
|
|
753
|
-
}
|