sveld 0.22.3 → 0.22.5

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.
@@ -29,6 +29,7 @@ interface ForwardedEvent {
29
29
  name: string;
30
30
  element: ComponentInlineElement | ComponentElement;
31
31
  description?: string;
32
+ detail?: string;
32
33
  }
33
34
  interface DispatchedEvent {
34
35
  type: "dispatched";
@@ -92,8 +93,8 @@ export default class ComponentParser {
92
93
  private static assignValue;
93
94
  private static formatComment;
94
95
  /**
95
- * Finds the last JSDoc block comment from an array of leading comments.
96
- * Filters out single-line comments and TypeScript directives.
96
+ * Finds the last comment from an array of leading comments.
97
+ * TypeScript directives are stripped before parsing, so we can safely take the last comment.
97
98
  */
98
99
  private static findJSDocComment;
99
100
  private sourceAtPos;
@@ -105,6 +106,10 @@ export default class ComponentParser {
105
106
  private addDispatchedEvent;
106
107
  private parseCustomTypes;
107
108
  cleanup(): void;
109
+ /**
110
+ * Strips TypeScript directive comments from script blocks only.
111
+ */
112
+ private static stripTypeScriptDirectivesFromScripts;
108
113
  parseSvelteComponent(source: string, diagnostics: ComponentParserDiagnostics): ParsedComponent;
109
114
  }
110
115
  export {};
@@ -78,19 +78,13 @@ class ComponentParser {
78
78
  return formatted_comment;
79
79
  }
80
80
  /**
81
- * Finds the last JSDoc block comment from an array of leading comments.
82
- * Filters out single-line comments and TypeScript directives.
81
+ * Finds the last comment from an array of leading comments.
82
+ * TypeScript directives are stripped before parsing, so we can safely take the last comment.
83
83
  */
84
84
  static findJSDocComment(leadingComments) {
85
- // Search from end to find the last JSDoc block comment
86
- for (let i = leadingComments.length - 1; i >= 0; i--) {
87
- const comment = leadingComments[i];
88
- // JSDoc comments are block comments (/* */) where the value starts with *
89
- if (comment.type === "Block" && comment.value.trimStart().startsWith("*")) {
90
- return comment;
91
- }
92
- }
93
- return undefined;
85
+ if (!leadingComments || leadingComments.length === 0)
86
+ return undefined;
87
+ return leadingComments[leadingComments.length - 1];
94
88
  }
95
89
  sourceAtPos(start, end) {
96
90
  return this.source?.slice(start, end);
@@ -107,8 +101,6 @@ class ComponentParser {
107
101
  return;
108
102
  if (this.props.has(prop_name)) {
109
103
  const existing_slot = this.props.get(prop_name);
110
- if (!existing_slot)
111
- return;
112
104
  this.props.set(prop_name, {
113
105
  ...existing_slot,
114
106
  ...data,
@@ -123,8 +115,6 @@ class ComponentParser {
123
115
  return;
124
116
  if (this.moduleExports.has(prop_name)) {
125
117
  const existing_slot = this.moduleExports.get(prop_name);
126
- if (!existing_slot)
127
- return;
128
118
  this.moduleExports.set(prop_name, {
129
119
  ...existing_slot,
130
120
  ...data,
@@ -147,8 +137,6 @@ class ComponentParser {
147
137
  const description = slot_description?.split("-").pop()?.trim();
148
138
  if (this.slots.has(name)) {
149
139
  const existing_slot = this.slots.get(name);
150
- if (!existing_slot)
151
- return;
152
140
  this.slots.set(name, {
153
141
  ...existing_slot,
154
142
  fallback,
@@ -262,14 +250,27 @@ class ComponentParser {
262
250
  this.generics = null;
263
251
  this.bindings.clear();
264
252
  }
253
+ /**
254
+ * Strips TypeScript directive comments from script blocks only.
255
+ */
256
+ static stripTypeScriptDirectivesFromScripts(source) {
257
+ // Find all script blocks and strip directives only from within them
258
+ return source.replace(/(<script[^>]*>)([\s\S]*?)(<\/script>)/gi, (_match, openTag, scriptContent, closeTag) => {
259
+ // Remove TypeScript directives from script content only
260
+ const cleanedContent = scriptContent.replace(/\/\/\s*@ts-[^\n\r]*/g, "");
261
+ return openTag + cleanedContent + closeTag;
262
+ });
263
+ }
265
264
  parseSvelteComponent(source, diagnostics) {
266
265
  if (this.options?.verbose) {
267
266
  console.log(`[parsing] "${diagnostics.moduleName}" ${diagnostics.filePath}`);
268
267
  }
269
268
  this.cleanup();
270
- this.source = source;
271
- this.compiled = (0, compiler_1.compile)(source);
272
- this.parsed = (0, compiler_1.parse)(source);
269
+ // Strip TypeScript directives from script blocks only to prevent interference with JSDoc
270
+ const cleanedSource = ComponentParser.stripTypeScriptDirectivesFromScripts(source);
271
+ this.source = cleanedSource;
272
+ this.compiled = (0, compiler_1.compile)(cleanedSource);
273
+ this.parsed = (0, compiler_1.parse)(cleanedSource);
273
274
  this.collectReactiveVars();
274
275
  this.parseCustomTypes();
275
276
  if (this.parsed?.module) {
@@ -554,8 +555,6 @@ class ComponentParser {
554
555
  const element_name = parent.name;
555
556
  if (this.bindings.has(prop_name)) {
556
557
  const existing_bindings = this.bindings.get(prop_name);
557
- if (!existing_bindings)
558
- return;
559
558
  if (!existing_bindings.elements.includes(element_name)) {
560
559
  this.bindings.set(prop_name, {
561
560
  ...existing_bindings,
@@ -601,12 +600,19 @@ class ComponentParser {
601
600
  if (event && event.type === "dispatched" && !actuallyDispatchedEvents.has(eventName)) {
602
601
  const description = this.eventDescriptions.get(eventName);
603
602
  const event_description = description?.split("-").pop()?.trim();
604
- this.events.set(eventName, {
603
+ const forwardedEvent = {
605
604
  type: "forwarded",
606
605
  name: eventName,
607
606
  element: element,
608
607
  description: event_description,
609
- });
608
+ };
609
+ // Preserve detail type if it was explicitly set in @event tag
610
+ // Note: "null" is a valid explicit type (e.g., @event {null} eventname)
611
+ // Only skip if detail is truly undefined or the string "undefined"
612
+ if (event.detail !== undefined && event.detail !== "undefined") {
613
+ forwardedEvent.detail = event.detail;
614
+ }
615
+ this.events.set(eventName, forwardedEvent);
610
616
  }
611
617
  });
612
618
  return {
@@ -168,6 +168,108 @@ function genEventDef(def) {
168
168
  return detail;
169
169
  return `CustomEvent<${detail}>`;
170
170
  };
171
+ // Check if an event name is a standard DOM event that exists in WindowEventMap
172
+ const isStandardDomEvent = (eventName) => {
173
+ // Standard DOM events that should use WindowEventMap
174
+ const standardEvents = new Set([
175
+ // Mouse events
176
+ "click",
177
+ "dblclick",
178
+ "mousedown",
179
+ "mouseup",
180
+ "mousemove",
181
+ "mouseover",
182
+ "mouseout",
183
+ "mouseenter",
184
+ "mouseleave",
185
+ "contextmenu",
186
+ "wheel",
187
+ // Keyboard events
188
+ "keydown",
189
+ "keyup",
190
+ "keypress",
191
+ // Form events
192
+ "submit",
193
+ "change",
194
+ "input",
195
+ "focus",
196
+ "blur",
197
+ "focusin",
198
+ "focusout",
199
+ "reset",
200
+ "select",
201
+ // Touch events
202
+ "touchstart",
203
+ "touchend",
204
+ "touchmove",
205
+ "touchcancel",
206
+ // Drag events
207
+ "drag",
208
+ "dragstart",
209
+ "dragend",
210
+ "dragover",
211
+ "dragenter",
212
+ "dragleave",
213
+ "drop",
214
+ // Pointer events
215
+ "pointerdown",
216
+ "pointerup",
217
+ "pointermove",
218
+ "pointerover",
219
+ "pointerout",
220
+ "pointerenter",
221
+ "pointerleave",
222
+ "pointercancel",
223
+ "gotpointercapture",
224
+ "lostpointercapture",
225
+ // Media events
226
+ "play",
227
+ "pause",
228
+ "ended",
229
+ "volumechange",
230
+ "timeupdate",
231
+ "loadeddata",
232
+ "loadedmetadata",
233
+ "canplay",
234
+ "canplaythrough",
235
+ "seeking",
236
+ "seeked",
237
+ "playing",
238
+ "waiting",
239
+ "stalled",
240
+ "suspend",
241
+ "abort",
242
+ "error",
243
+ "emptied",
244
+ "ratechange",
245
+ "durationchange",
246
+ "loadstart",
247
+ "progress",
248
+ "loadend",
249
+ // Animation/Transition events
250
+ "animationstart",
251
+ "animationend",
252
+ "animationiteration",
253
+ "animationcancel",
254
+ "transitionstart",
255
+ "transitionend",
256
+ "transitionrun",
257
+ "transitioncancel",
258
+ // Other events
259
+ "scroll",
260
+ "resize",
261
+ "load",
262
+ "unload",
263
+ "beforeunload",
264
+ "cut",
265
+ "copy",
266
+ "paste",
267
+ "compositionstart",
268
+ "compositionupdate",
269
+ "compositionend",
270
+ ]);
271
+ return standardEvents.has(eventName);
272
+ };
171
273
  if (def.events.length === 0)
172
274
  return EMPTY_EVENTS;
173
275
  const events_map = def.events
@@ -176,7 +278,33 @@ function genEventDef(def) {
176
278
  if (event.description) {
177
279
  description = `/** ${event.description} */\n`;
178
280
  }
179
- return `${description}${clampKey(event.name)}: ${event.type === "dispatched" ? createDispatchedEvent(event.detail) : `${mapEvent()}["${event.name}"]`};\n`;
281
+ let eventType;
282
+ if (event.type === "dispatched") {
283
+ eventType = createDispatchedEvent(event.detail);
284
+ }
285
+ else {
286
+ // For forwarded events, determine the type based on @event JSDoc and element/event type
287
+ const elementName = typeof event.element === "string" ? event.element : event.element.name;
288
+ const isComponent = elementName && /^[A-Z]/.test(elementName);
289
+ const isStandardEvent = !isComponent || isStandardDomEvent(event.name);
290
+ // Check if there's an explicit non-null detail type from @event JSDoc
291
+ // Note: detail="null" on standard DOM events is treated as "not explicitly typed"
292
+ // because @event click (without {type}) defaults to null but shouldn't override WindowEventMap
293
+ const hasExplicitNonNullDetail = event.detail !== undefined && event.detail !== "undefined" && !(event.detail === "null" && isStandardEvent);
294
+ if (hasExplicitNonNullDetail) {
295
+ // If @event tag explicitly provides a non-null detail type, always use it (highest priority)
296
+ eventType = createDispatchedEvent(event.detail);
297
+ }
298
+ else if (isStandardEvent) {
299
+ // Standard DOM event (native element or standard event name) without explicit type
300
+ eventType = `${mapEvent()}["${event.name}"]`;
301
+ }
302
+ else {
303
+ // Custom event from component with no explicit type or explicit null
304
+ eventType = event.detail === "null" ? createDispatchedEvent("null") : createDispatchedEvent();
305
+ }
306
+ }
307
+ return `${description}${clampKey(event.name)}: ${eventType};\n`;
180
308
  })
181
309
  .join("");
182
310
  return `{${events_map}}`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sveld",
3
- "version": "0.22.3",
3
+ "version": "0.22.5",
4
4
  "license": "Apache-2.0",
5
5
  "description": "Generate TypeScript definitions for your Svelte components.",
6
6
  "main": "./lib/index.js",