sveld 0.22.3 → 0.22.4

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) {
@@ -484,7 +485,7 @@ class ComponentParser {
484
485
  replace: false,
485
486
  };
486
487
  if (value === undefined)
487
- return slot_props;
488
+ return {};
488
489
  if (value[0]) {
489
490
  const { type, expression, raw, start, end } = value[0];
490
491
  if (type === "Text") {
@@ -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,18 @@ 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
+ // Only preserve detail if it was explicitly set and is not "null"
610
+ // (null means no detail was specified in @event tag)
611
+ if (event.detail !== undefined && event.detail !== "null") {
612
+ forwardedEvent.detail = event.detail;
613
+ }
614
+ this.events.set(eventName, forwardedEvent);
610
615
  }
611
616
  });
612
617
  return {
@@ -634,9 +639,16 @@ class ComponentParser {
634
639
  if (slot_props[key].replace && slot_props[key].value !== undefined) {
635
640
  slot_props[key].value = this.props.get(slot_props[key].value)?.type;
636
641
  }
637
- if (slot_props[key].value === undefined)
638
- slot_props[key].value = "any";
639
- new_props.push(`${key}: ${slot_props[key].value}`);
642
+ let propValue = slot_props[key].value;
643
+ if (propValue === undefined) {
644
+ propValue = "any";
645
+ }
646
+ else if (typeof propValue === "string" && propValue.startsWith("{")) {
647
+ // If the value starts with {, it's a complex expression
648
+ // Use 'any' to avoid invalid TypeScript syntax
649
+ propValue = "any";
650
+ }
651
+ new_props.push(`${key}: ${propValue}`);
640
652
  });
641
653
  const formatted_slot_props = new_props.length === 0 ? "{}" : `{ ${new_props.join(", ")} }`;
642
654
  return { ...slot, slot_props: formatted_slot_props };
@@ -176,7 +176,25 @@ function genEventDef(def) {
176
176
  if (event.description) {
177
177
  description = `/** ${event.description} */\n`;
178
178
  }
179
- return `${description}${clampKey(event.name)}: ${event.type === "dispatched" ? createDispatchedEvent(event.detail) : `${mapEvent()}["${event.name}"]`};\n`;
179
+ let eventType;
180
+ if (event.type === "dispatched") {
181
+ eventType = createDispatchedEvent(event.detail);
182
+ }
183
+ else {
184
+ // For forwarded events, check if it's from a native HTML element or a component
185
+ // If element starts with uppercase, it's a component, so treat as CustomEvent
186
+ // Also check if the forwarded event has detail type information preserved
187
+ const elementName = typeof event.element === "string" ? event.element : event.element.name;
188
+ const isComponent = elementName && /^[A-Z]/.test(elementName);
189
+ const hasDetail = event.detail && event.detail !== "undefined" && event.detail !== "null";
190
+ if (isComponent || hasDetail) {
191
+ eventType = createDispatchedEvent(event.detail);
192
+ }
193
+ else {
194
+ eventType = `${mapEvent()}["${event.name}"]`;
195
+ }
196
+ }
197
+ return `${description}${clampKey(event.name)}: ${eventType};\n`;
180
198
  })
181
199
  .join("");
182
200
  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.4",
4
4
  "license": "Apache-2.0",
5
5
  "description": "Generate TypeScript definitions for your Svelte components.",
6
6
  "main": "./lib/index.js",