sveld 0.29.0 → 0.30.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 CHANGED
@@ -11,6 +11,8 @@ The purpose of this project is to make third party Svelte component libraries co
11
11
 
12
12
  `sveld` uses the Svelte 5 compiler to parse `.svelte` files. That single parse path powers docgen and TypeScript output for Svelte 3, Svelte 4, Svelte 5 without runes (`export let`, `<slot>`, `$$restProps`, …), and Svelte 5 Runes (`$props()`, `$bindable()`, `{@render ...}`, callback props such as `onclick`, …).
13
13
 
14
+ For `lang="ts"` components, `sveld` preserves source-level prop type annotations when possible instead of requiring JSDoc as the primary source of truth. This includes legacy `export let` props, typed `$props()` destructuring, typed whole-object `$props()` captures, local `interface`/`type` declarations, and imported type references in emitted `.d.ts` files.
15
+
14
16
  | Syntax mode | Supported |
15
17
  | :------------------- | :-------: |
16
18
  | Svelte 3 | ✓ |
@@ -129,7 +131,7 @@ export default class Button extends SvelteComponentTyped<
129
131
  - [@type](#type)
130
132
  - [@typedef](#typedef)
131
133
  - [@callback](#callback)
132
- - [@slot](#slot)
134
+ - [@slot / @snippet](#slot--snippet)
133
135
  - [Svelte 5 Snippet Compatibility](#svelte-5-snippet-compatibility)
134
136
  - [@event](#event)
135
137
  - [Context API](#context-api)
@@ -156,6 +158,15 @@ Extracted metadata include:
156
158
 
157
159
  This library adopts a progressively enhanced approach. Any property type that cannot be inferred (e.g., "hello" is a string) falls back to "any" to minimize incorrectly typed properties or signatures. To mitigate this, the library author can add JSDoc annotations to specify types that cannot be reliably inferred. This represents a progressively enhanced approach because JSDocs are comments that can be ignored by the compiler.
158
160
 
161
+ When both TypeScript syntax and JSDoc are present, `sveld` resolves prop types in this order:
162
+
163
+ 1. explicit TypeScript annotation
164
+ 2. explicit JSDoc annotation
165
+ 3. initializer inference
166
+ 4. `any`
167
+
168
+ `sveld` intentionally stays AST-only. It preserves imported and local type text in generated `.d.ts` output, but it does not perform project-wide semantic resolution with the TypeScript compiler. That means opaque imported whole-object `$props()` types can be preserved in declarations without being fully expanded into JSON metadata.
169
+
159
170
  ## Usage
160
171
 
161
172
  ### Installation
@@ -204,13 +215,15 @@ sveld({
204
215
  When building the library, TypeScript definitions are emitted to the `types` folder by default.
205
216
 
206
217
  Customize the output folder using the `typesOptions.outDir` option.
218
+ Use `typesOptions.printWidth` to control Prettier wrapping for generated `.d.ts` files. The default is `80`.
207
219
 
208
220
  The following example emits the output to the `dist` folder:
209
221
 
210
222
  ```diff
211
223
  sveld({
212
224
  + typesOptions: {
213
- + outDir: 'dist'
225
+ + outDir: 'dist',
226
+ + printWidth: 80
214
227
  + }
215
228
  })
216
229
  ```
@@ -300,7 +313,7 @@ TypeScript definitions are outputted to the `types` folder by default. Don't for
300
313
  - **`entry`** (string, optional): Specify the entry point to uncompiled Svelte source. If not provided, sveld will use the `"svelte"` field from `package.json`.
301
314
  - **`glob`** (boolean, optional): Enable glob mode to analyze all `*.svelte` files.
302
315
  - **`types`** (boolean, optional, default: `true`): Generate TypeScript definitions.
303
- - **`typesOptions`** (object, optional): Options for TypeScript definition generation.
316
+ - **`typesOptions`** (object, optional): Options for TypeScript definition generation, including `outDir`, `preamble`, and `printWidth`.
304
317
  - **`json`** (boolean, optional): Generate component documentation in JSON format.
305
318
  - **`jsonOptions`** (object, optional): Options for JSON output.
306
319
  - **`markdown`** (boolean, optional): Generate component documentation in Markdown format.
@@ -354,6 +367,8 @@ export let id = `ccs-${Math.random().toString(36)}`;
354
367
 
355
368
  Use the `@type` tag to explicitly document the type. In the following example, the `kind` property has an enumerated (enum) type.
356
369
 
370
+ For `lang="ts"` components, prefer native TypeScript annotations when they are already present. `@type` remains useful for JavaScript components, for overriding inferred types, and for cases where the AST cannot recover a more precise type.
371
+
357
372
  **Signature:**
358
373
 
359
374
  ```js
@@ -369,18 +384,23 @@ Use the `@type` tag to explicitly document the type. In the following example, t
369
384
 
370
385
  ```svelte
371
386
  <script>
372
- /**
373
- * Specify the kind of button
374
- * @type {"primary" | "secondary" | "tertiary"}
375
- */
376
- /**
377
- * Specify the Carbon icon to render
378
- * @type {typeof import("carbon-icons-svelte").CarbonIcon}
379
- */
380
- let { kind = "primary", renderIcon = Close20 } = $props();
387
+ let {
388
+ /**
389
+ * Specify the kind of button
390
+ * @type {"primary" | "secondary" | "tertiary"}
391
+ */
392
+ kind = "primary",
393
+ /**
394
+ * Specify the Carbon icon to render
395
+ * @type {typeof import("carbon-icons-svelte").CarbonIcon}
396
+ */
397
+ renderIcon = Close20,
398
+ } = $props();
381
399
  </script>
382
400
  ```
383
401
 
402
+ For runes components with multiple destructured props, place JSDoc on the individual property you want to document. A declaration-level JSDoc block is only used as a fallback when the destructure exposes a single public prop.
403
+
384
404
  **Svelte 3, 4, 5 (non-Runes):**
385
405
 
386
406
  ```svelte
@@ -680,9 +700,9 @@ Callbacks can be combined with `@typedef` in the same comment block:
680
700
 
681
701
  When `@returns` is omitted, the return type defaults to `void`. When no `@param` tags are present, the callback is typed as a no-argument function.
682
702
 
683
- ### `@slot`
703
+ ### `@slot` / `@snippet`
684
704
 
685
- Use the `@slot` tag for typing component slots. Note that `@slot` is a non-standard JSDoc tag.
705
+ Use the `@slot` tag for typing component slots. For Svelte 5 runes components, `@snippet` is also supported as an alias. Both are non-standard JSDoc tags.
686
706
 
687
707
  Descriptions are optional for named slots. Currently, the default slot cannot have a description.
688
708
 
@@ -691,12 +711,14 @@ Descriptions are optional for named slots. Currently, the default slot cannot ha
691
711
  ```js
692
712
  /**
693
713
  * @slot {Type} slot-name [slot description]
714
+ * @snippet {Type} snippet-name [snippet description]
694
715
  */
695
716
 
696
717
  Omit the `slot-name` to type the default slot.
697
718
 
698
719
  /**
699
720
  * @slot {Type}
721
+ * @snippet {Type}
700
722
  */
701
723
  ```
702
724
 
@@ -707,9 +729,9 @@ Omit the `slot-name` to type the default slot.
707
729
  ```svelte
708
730
  <script>
709
731
  /**
710
- * @slot {{ prop: number; doubled: number; }}
711
- * @slot {{}} title
712
- * @slot {{ prop: number }} body - Customize the paragraph text.
732
+ * @snippet {{ prop: number; doubled: number; }}
733
+ * @snippet {{}} title
734
+ * @snippet {{ prop: number }} body - Customize the paragraph text.
713
735
  */
714
736
 
715
737
  let { prop = 0, children, title, body } = $props();
@@ -752,7 +774,9 @@ Omit the `slot-name` to type the default slot.
752
774
 
753
775
  For Svelte 5 compatibility, `sveld` automatically generates optional snippet props for all slots. This allows consumers to use either the traditional slot syntax or Svelte 5's `{#snippet}` syntax.
754
776
 
755
- When parsing runes components, `sveld` also maps `{@render ...}` calls back into the same slot metadata used for traditional `<slot>` declarations.
777
+ When parsing runes components, `sveld` maps `{@render ...}` calls back into the same slot metadata used for traditional `<slot>` declarations. Reserved snippet props such as `children`, along with named snippet props discovered from `{@render ...}`, are represented through `slots` metadata and generated snippet prop types rather than duplicated in the `props` output.
778
+
779
+ Positional snippet calls such as `{@render row?.(item, index)}` are preserved as typed props when the prop itself has an explicit type like `Snippet<[Item, number]>`. They are not converted into synthetic slot metadata.
756
780
 
757
781
  For slots with props (e.g., `let:prop`), the generated type uses a Snippet-compatible signature:
758
782
 
@@ -856,7 +880,7 @@ export default class DataTable<Row> extends SvelteComponentTyped<
856
880
 
857
881
  Use the `@event` tag to type dispatched events. An event name is required and a description optional.
858
882
 
859
- In Svelte 5 runes components, callback props such as `onclick` are treated as component props, not events. The `events` output remains reserved for dispatched events and legacy forwarded events.
883
+ In Svelte 5 runes components, callback props such as `onclick` are treated as component props, not events. The `events` output remains reserved for real dispatched events and legacy forwarded events. If a runes component documents `@event foo` and exposes a matching callback prop like `onfoo` without actually dispatching or forwarding `foo`, `sveld` aliases that documentation onto the callback prop instead of synthesizing an emitted event.
860
884
 
861
885
  Use `null` as the value if no event detail is provided.
862
886
 
@@ -873,6 +897,20 @@ Use `null` as the value if no event detail is provided.
873
897
 
874
898
  **Svelte 5 Runes:**
875
899
 
900
+ ```svelte
901
+ <script>
902
+ /**
903
+ * Fired when a value is saved.
904
+ * @event {{ id: string }} save
905
+ */
906
+ let { onsave } = $props();
907
+ </script>
908
+
909
+ <button onclick={() => onsave?.({ id: "1" })}>Save</button>
910
+ ```
911
+
912
+ **Svelte 5 Runes with dispatched events:**
913
+
876
914
  ```svelte
877
915
  <script>
878
916
  /**
@@ -1439,8 +1477,13 @@ Because `sveld` is designed to support JavaScript-only usage as a baseline, the
1439
1477
  * @generics {Row extends DataTableRow = DataTableRow} Row
1440
1478
  */
1441
1479
 
1442
- /** @type {ReadonlyArray<DataTableHeader<Row>>} */
1443
- let { headers = [], rows = [], children } = $props();
1480
+ let {
1481
+ /** @type {ReadonlyArray<DataTableHeader<Row>>} */
1482
+ headers = [],
1483
+ /** @type {ReadonlyArray<Row>} */
1484
+ rows = [],
1485
+ children,
1486
+ } = $props();
1444
1487
  </script>
1445
1488
 
1446
1489
  {@render children?.({ headers, rows })}
@@ -1,3 +1,13 @@
1
+ export interface ParsedComponentTypeScriptMetadata {
2
+ canonicalPropsType?: string;
3
+ canonicalPropNames: string[];
4
+ localTypeDeclarations: string[];
5
+ typeImportStatements: string[];
6
+ }
7
+ export declare const PARSED_COMPONENT_TYPE_SCRIPT_METADATA: unique symbol;
8
+ export declare function getParsedComponentTypeScriptMetadata(component: {
9
+ [PARSED_COMPONENT_TYPE_SCRIPT_METADATA]?: ParsedComponentTypeScriptMetadata;
10
+ }): ParsedComponentTypeScriptMetadata | undefined;
1
11
  /**
2
12
  * Diagnostic information for component parsing.
3
13
  *
@@ -80,7 +90,7 @@ interface ComponentSlot {
80
90
  fallback?: string;
81
91
  /** TypeScript type definition for slot props (e.g., "{ title: string }") */
82
92
  slot_props?: string;
83
- /** Description extracted from JSDoc `@slot` tags */
93
+ /** Description extracted from JSDoc `@slot` or `@snippet` tags */
84
94
  description?: string;
85
95
  }
86
96
  /**
@@ -258,6 +268,8 @@ export interface ParsedComponent {
258
268
  componentComment?: string;
259
269
  /** Contexts created with `setContext` in the component */
260
270
  contexts?: ComponentContext[];
271
+ /** Internal writer-only TypeScript metadata. Not serialized to JSON. */
272
+ [PARSED_COMPONENT_TYPE_SCRIPT_METADATA]?: ParsedComponentTypeScriptMetadata;
261
273
  }
262
274
  export default class ComponentParser {
263
275
  /** Parser configuration options (e.g., verbose logging) */
@@ -306,8 +318,20 @@ export default class ComponentParser {
306
318
  private readonly propLocalToPublicName;
307
319
  /** Tracks `$props()` bindings that are used as spread/rest props */
308
320
  private readonly restPropLocals;
321
+ /** Tracks identifier bindings that capture the entire `$props()` object */
322
+ private readonly wholePropsLocals;
309
323
  /** Tracks prop locals that are used as snippet/render props */
310
324
  private readonly snippetPropLocals;
325
+ /** Per-declarator type metadata extracted from modern AST `$props()` annotations */
326
+ private readonly runesPropsDeclarationMetadataByDeclaratorStart;
327
+ /** Explicit TypeScript prop annotations for legacy `export let` declarations keyed by local name */
328
+ private readonly explicitPropTypesByName;
329
+ /** Type-only imports keyed by their local binding names */
330
+ private readonly typeImportBindingsByLocalName;
331
+ /** Local interface/type declarations keyed by type name */
332
+ private readonly localTypeDeclarationsByName;
333
+ /** Typed `$props()` declarations discovered in source order */
334
+ private readonly typedRunesPropsDeclarations;
311
335
  /** Component-level lexical scope shared by instance script and template */
312
336
  private readonly componentScope;
313
337
  /** Precomputed lexical scopes for nested AST nodes */
@@ -323,6 +347,16 @@ export default class ComponentParser {
323
347
  private trackPropLocalName;
324
348
  private getPropByLocalOrPublic;
325
349
  private getPropTypeByLocalOrPublic;
350
+ private getExplicitPropType;
351
+ private getRunesPropsDeclarationMetadata;
352
+ private getRunesPropTypeMetadata;
353
+ private getTypeReferenceName;
354
+ private getTypeDependencyName;
355
+ private getTypeAnnotationText;
356
+ private collectReferencedTypeDependencies;
357
+ private buildTypeImportStatements;
358
+ private buildTypeScriptMetadata;
359
+ private buildRunesPropTypeMetadataMap;
326
360
  private declareScopeBinding;
327
361
  private resolveIdentifierToReactiveProp;
328
362
  private collectPatternIdentifiers;
@@ -347,7 +381,7 @@ export default class ComponentParser {
347
381
  * Extracts and categorizes JSDoc tags from a parsed comment.
348
382
  *
349
383
  * Separates tags into type, param, returns, and additional categories while
350
- * excluding tags that are handled separately (extends, restProps, slot, event, typedef).
384
+ * excluding tags that are handled separately (extends, restProps, slot/snippet, event, typedef).
351
385
  *
352
386
  * @param parsed - The parsed comment result from comment-parser
353
387
  * @returns An object containing categorized tags and the comment description
@@ -390,6 +424,7 @@ export default class ComponentParser {
390
424
  private static findJSDocComment;
391
425
  private findAdjacentJSDocComment;
392
426
  private processNodeJSDoc;
427
+ private processLeadingCommentsJSDoc;
393
428
  /**
394
429
  * Processes JSDoc comments from leadingComments and extracts structured information.
395
430
  *
@@ -423,6 +458,7 @@ export default class ComponentParser {
423
458
  * ```
424
459
  */
425
460
  private processJSDocComment;
461
+ private buildRunesPropTypeMetadata;
426
462
  /**
427
463
  * Checks if a MemberExpression represents a well-known numeric constant.
428
464
  *
@@ -494,6 +530,7 @@ export default class ComponentParser {
494
530
  private parseRunesPropsDeclaration;
495
531
  private inferSlotPropValueFromExpression;
496
532
  private buildSlotPropsFromObjectExpression;
533
+ private resolveRenderTagPropReference;
497
534
  private extractRenderTagInfo;
498
535
  /**
499
536
  * Adds or merges a component prop to the props map.
@@ -640,6 +677,7 @@ export default class ComponentParser {
640
677
  * ```
641
678
  */
642
679
  private addDispatchedEvent;
680
+ private normalizeRunesCallbackProps;
643
681
  /**
644
682
  * Parses custom types, events, slots, and other JSDoc annotations from component comments.
645
683
  *