sveld 0.37.4 → 0.37.6

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
@@ -429,11 +429,13 @@ Every diagnostic carries a stable, namespaced `code` (`"sveld/<kind>"`) alongsid
429
429
  | `sveld/context-any-type` | `warning` | Annotate the `setContext` value's declaration with `@type` or a native TypeScript type. |
430
430
  | `sveld/slot-missing-type` | `warning` | Add the required `{Type}` annotation to the `@slot`/`@snippet` tag (e.g. `@slot {{}} name`); until then it falls back to `Record<string, never>`. |
431
431
  | `sveld/event-no-source` | `warning` | Dispatch the event (`createEventDispatcher`/`dispatch`), forward it (`on:name`), or add a matching `on<Name>` callback prop; otherwise remove the stale `@event` tag. |
432
+ | `sveld/dispatch-escapes` | `warning` | The `createEventDispatcher()` result is passed to a function sveld can't follow: one that isn't imported from a local module, names an event with something other than a string literal, or passes the dispatcher on. Document the events it dispatches with `@event` tags. Add `@sveld-ignore sveld/dispatch-escapes` to the dispatcher's JSDoc once they're covered. |
432
433
  | `sveld/example-compile-error` | `error` | Fix the `@example` TS/JS code block so it type-checks, or remove the broken example. |
433
434
  | `sveld/example-syntax-error` | `error` | Fix the `@example` `svelte`/`html` markup so it parses, or remove the broken example. |
434
435
  | `sveld/syntax-skipped` | `error` | Rewrite the flagged syntax in a form sveld can model (see the diagnostic's `message` for what was skipped). |
435
436
  | `sveld/rest-props-unresolved` | `warning` | Spread `$$restProps` onto a plain element (or `svelte:element`) instead of a component, or add an `@restProps` tag to type it manually. |
436
437
  | `sveld/context-duplicate-key` | `warning` | Remove the duplicate `setContext` call, or give it a distinct key; only the first call's shape is used. |
438
+ | `sveld/context-key-unresolved` | `warning` | Use a string literal, a `const`-bound string, `Symbol()`, or a string `export const` imported from a relative module as the `setContext` key; otherwise the context is left out of every output. |
437
439
  | `sveld/spread-unresolved` | `warning` | Spread a local object literal or a variable with a resolvable type instead; otherwise the spread widens the generated type to `Record<string, any>`. |
438
440
  | `sveld/export-unresolved` | `warning` | Export a local declaration directly. Instance-script exports are props, so move a re-export (`export { x } from "..."`, or `export { x }` of an import) into `<script context="module">`, where sveld writes it to the `.d.ts` as-is. |
439
441
  | `sveld/module-export-conflict` | `warning` | Rename the module-script export. `default` is always skipped (it collides with the component itself); a name matching the generated `<Name>Props`/`<Name>Exports` type breaks the `.d.ts` if the export carries a type. |
@@ -451,7 +453,7 @@ Every diagnostic carries a stable, namespaced `code` (`"sveld/<kind>"`) alongsid
451
453
 
452
454
  #### Severity and `--strict=errors`
453
455
 
454
- Each diagnostic's `severity` is `"error"` (`example-compile-error`, `example-syntax-error`, `syntax-skipped`, `extend-props-target-missing`, `internal-typedef-referenced` — sveld emitted broken or unmodeled output) or `"warning"` (`prop-unknown-type`, `context-any-type`, `slot-missing-type`, `event-no-source`, `rest-props-unresolved`, `context-duplicate-key`, `spread-unresolved`, `export-unresolved`, `module-export-conflict`, `extend-props-duplicate`, `extend-props-override`, `jsdoc-unknown-tag`, `typedef-duplicate`, `property-duplicate`, `generics-conflict`, `event-description-ambiguous`, `jsdoc-tag-dropped`, `types-inline-unresolved` — a type fell back to `any`). Plain `strict: true` / `--strict` fails on both, unchanged from before. Pass `strict: "errors"` (or `--strict=errors`) to fail CI only on `error`-severity diagnostics, letting `any`-fallback warnings through:
456
+ Each diagnostic's `severity` is `"error"` (`example-compile-error`, `example-syntax-error`, `syntax-skipped`, `extend-props-target-missing`, `internal-typedef-referenced` — sveld emitted broken or unmodeled output) or `"warning"` (`prop-unknown-type`, `context-any-type`, `slot-missing-type`, `event-no-source`, `dispatch-escapes`, `rest-props-unresolved`, `context-duplicate-key`, `context-key-unresolved`, `spread-unresolved`, `export-unresolved`, `module-export-conflict`, `extend-props-duplicate`, `extend-props-override`, `jsdoc-unknown-tag`, `typedef-duplicate`, `property-duplicate`, `generics-conflict`, `event-description-ambiguous`, `jsdoc-tag-dropped`, `types-inline-unresolved` — a type fell back to `any`). Plain `strict: true` / `--strict` fails on both, unchanged from before. Pass `strict: "errors"` (or `--strict=errors`) to fail CI only on `error`-severity diagnostics, letting `any`-fallback warnings through:
455
457
 
456
458
  ```sh
457
459
  npx sveld --json --strict=errors
@@ -481,7 +483,7 @@ export default defineConfig({
481
483
  });
482
484
  ```
483
485
 
484
- **Inline `@sveld-ignore <code>`**, on the same JSDoc comment as the prop, `@event` tag, or context variable it applies to:
486
+ **Inline `@sveld-ignore <code>`**, on the same JSDoc comment as the prop, `@event` tag, context variable, or event dispatcher it applies to:
485
487
 
486
488
  ```svelte
487
489
  <script>
@@ -2187,7 +2189,27 @@ Chained references are also resolved:
2187
2189
  count?: number;
2188
2190
  ```
2189
2191
 
2190
- Resolution follows up to 5 levels of indirection. Beyond that, the last resolved identifier name is used as the default value. If the identifier cannot be resolved (e.g., it is imported from another module), the variable name is used as-is.
2192
+ Resolution follows up to 5 levels of indirection. Beyond that, the last resolved identifier name is used as the default value.
2193
+
2194
+ A named import resolves when the imported module (followed through re-exports) declares it as an `export const` with a string, number, boolean, or static template literal:
2195
+
2196
+ ```svelte
2197
+ <script>
2198
+ // timing.js: export const TOOLTIP_LEAVE_DELAY_MS = 300;
2199
+ import { TOOLTIP_LEAVE_DELAY_MS } from "./timing.js";
2200
+
2201
+ export let leaveDelayMs = TOOLTIP_LEAVE_DELAY_MS;
2202
+ </script>
2203
+ ```
2204
+
2205
+ ```ts
2206
+ /**
2207
+ * @default 300
2208
+ */
2209
+ leaveDelayMs?: number;
2210
+ ```
2211
+
2212
+ Any other identifier that cannot be resolved (an `export let`, a computed value, a package import) is used as-is, and its type falls back to `any`.
2191
2213
 
2192
2214
  When an explicit `@default` annotation is provided, it always takes precedence over the resolved value.
2193
2215
 
@@ -2679,6 +2701,16 @@ A description can wrap onto continuation lines, which run until the next tag. In
2679
2701
  */
2680
2702
  ```
2681
2703
 
2704
+ A `{type}` can wrap too. The text after its closing `}` and the name is the tag's own description, exactly as if the type fit on one line:
2705
+
2706
+ ```js
2707
+ /**
2708
+ * @property {"ascending"
2709
+ * | "descending"} [dir] - The next sort direction,
2710
+ * reported regardless.
2711
+ */
2712
+ ```
2713
+
2682
2714
  ### `@callback`
2683
2715
 
2684
2716
  The `@callback` tag defines a function type with `@param` and `@returns`, following the [TypeScript JSDoc `@callback` spec](https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html#callback). Like `@typedef`, callbacks are exported from the generated `.d.ts`.
@@ -2981,6 +3013,28 @@ In Svelte 5 runes components, callback props like `onclick` are props, not event
2981
3013
 
2982
3014
  Use `null` as the value if no event detail is provided.
2983
3015
 
3016
+ `sveld` infers events from `dispatch("name")` calls. When the dispatcher is passed to a function imported from a local module, as `helper(dispatch)` or `helper({ dispatch })`, it reads that function too and picks up the events it dispatches:
3017
+
3018
+ ```js
3019
+ // dispatch-open-close.js
3020
+ export function createOpenCloseDispatcher(dispatch) {
3021
+ return (open) => dispatch(open ? "open" : "close");
3022
+ }
3023
+ ```
3024
+
3025
+ ```svelte
3026
+ <script>
3027
+ import { createEventDispatcher } from "svelte";
3028
+ import { createOpenCloseDispatcher } from "./dispatch-open-close.js";
3029
+
3030
+ const dispatch = createEventDispatcher();
3031
+ // Types `open` and `close` as `CustomEvent<null>`
3032
+ const notifyOpenChange = createOpenCloseDispatcher(dispatch);
3033
+ </script>
3034
+ ```
3035
+
3036
+ This happens when sveld builds the whole library (CLI, `sveld()`, or the Vite plugin), not when parsing a single component on its own. Each event name there must be a string literal, or a conditional between them. The detail is typed from a literal argument and is `any` otherwise, so add an `@event` tag to type it more precisely. When sveld can't follow the dispatcher (a package import, a local function, a computed event name, or a helper that passes the dispatcher on), it reports `sveld/dispatch-escapes`, and you document those events with `@event` tags.
3037
+
2984
3038
  **Signature:**
2985
3039
 
2986
3040
  ```js
@@ -3634,12 +3688,15 @@ The key becomes the `{PascalCase}Context` type name. `sveld` can resolve:
3634
3688
  | Static template literal | `` setContext(`simple-modal`, …) `` | `SimpleModalContext` |
3635
3689
  | `const`-bound string | `const KEY = "simple-modal";`<br>`setContext(KEY, …)` | `SimpleModalContext` |
3636
3690
  | `Symbol()` / `Symbol.for()` | `setContext(Symbol("tabs"), …)` | `TabsContext` |
3691
+ | Imported `export const` string | `import { KEY } from "./keys.js";`<br>`setContext(KEY, …)` | `SimpleModalContext` |
3637
3692
 
3638
3693
  `const` identifiers are followed up to 5 levels deep (`const A = "x"; const B = A;`). Only `const` bindings count. `let`, `var`, and props are skipped because they can change at runtime.
3639
3694
 
3640
3695
  Symbol keys take their name from the description: `Symbol("tabs")` and `Symbol.for("tabs")` both become `TabsContext`. For `const ModalKey = Symbol()` with no description, the binding name wins: `ModalKeyContext`.
3641
3696
 
3642
- Anything else (dynamic identifiers, template interpolation, other function calls) logs a warning. No context type is generated.
3697
+ An imported key is read from its module, following re-exports, when that module is a relative `.js`/`.ts` file declaring it as `export const KEY = "simple-modal"` (or a static template literal). This works when sveld builds a whole library (CLI, `sveld()`, or the Vite plugin), not when parsing a single component on its own.
3698
+
3699
+ Anything else (dynamic identifiers, `let` exports, template interpolation, other function calls) records a `sveld/context-key-unresolved` diagnostic. No context type is generated.
3643
3700
 
3644
3701
  #### Example
3645
3702
 
@@ -3819,7 +3876,7 @@ There are several ways to type contexts:
3819
3876
 
3820
3877
  #### Notes
3821
3878
 
3822
- - Context keys must be statically resolvable: a string literal, a static template literal, a `const`-bound string, or a `Symbol()` / `Symbol.for()` call with a static description. Dynamic expressions (runtime identifiers, template interpolation, other function calls) are skipped with a warning.
3879
+ - Context keys must be statically resolvable: a string literal, a static template literal, a `const`-bound string (local or an imported `export const`), or a `Symbol()` / `Symbol.for()` call with a static description. Dynamic expressions (runtime identifiers, template interpolation, other function calls) are skipped with a `sveld/context-key-unresolved` diagnostic.
3823
3880
  - Variables passed to `setContext` should have JSDoc `@type` annotations for accurate types
3824
3881
  - The generated type name follows the pattern: `{PascalCase}Context`. Separators (hyphens, underscores, dots, colons, slashes, spaces) are stripped and each segment is capitalized:
3825
3882
  | Context Key | Generated Type Name |
package/lib/browser.d.ts CHANGED
@@ -37,6 +37,13 @@ interface PendingCallDefaultCandidate {
37
37
  importedName?: string;
38
38
  }
39
39
 
40
+ interface PendingConstDefaultCandidate {
41
+ propName: string;
42
+ location: "props" | "moduleExports";
43
+ importSource: string;
44
+ importedName: string;
45
+ }
46
+
40
47
  interface PendingContextKeyCandidate {
41
48
  importSource: string;
42
49
  importedName: string;
@@ -46,6 +53,23 @@ interface PendingContextKeyCandidate {
46
53
  source?: SourceRange;
47
54
  }
48
55
 
56
+ interface PendingDispatchEscapeCandidate {
57
+ importSource: string;
58
+ importedName: string;
59
+ /** Callee as written, for messages. */
60
+ calleeText: string;
61
+ /** Local name of the `createEventDispatcher()` result. */
62
+ dispatcherName: string;
63
+ /** Argument position the dispatcher is passed at. */
64
+ argumentIndex: number;
65
+ /** Set when passed as an object literal property (`{ dispatch }`): that property's key. */
66
+ property?: string;
67
+ /** Source range of the call, when available. */
68
+ source?: SourceRange;
69
+ /** `@sveld-ignore sveld/dispatch-escapes` on the dispatcher's declaration. */
70
+ ignored?: boolean;
71
+ }
72
+
49
73
  interface ParsedComponentTypeScriptMetadata {
50
74
  canonicalPropsType?: string;
51
75
  canonicalPropNames: string[];
@@ -60,8 +84,17 @@ interface ParsedComponentTypeScriptMetadata {
60
84
  referencesComponentGenerics?: boolean;
61
85
  /** Unresolved CallExpression defaults for the cross-file pass in `generateBundle`. */
62
86
  pendingCallDefaultCandidates?: PendingCallDefaultCandidate[];
87
+ /** Imported-identifier defaults for the cross-file pass in `generateBundle`. */
88
+ pendingConstDefaultCandidates?: PendingConstDefaultCandidate[];
63
89
  /** Unresolved `setContext` import keys for the cross-file pass in `generateBundle`. */
64
90
  pendingContextKeyCandidates?: PendingContextKeyCandidate[];
91
+ /** Dispatchers passed to imported functions, for the cross-file pass in `generateBundle`. */
92
+ pendingDispatchEscapeCandidates?: PendingDispatchEscapeCandidate[];
93
+ /**
94
+ * `event-no-source` diagnostics held back while the dispatcher escapes to
95
+ * imported functions: `generateBundle` keeps those the functions don't dispatch.
96
+ */
97
+ deferredEventNoSourceDiagnostics?: SveldDiagnostic[];
65
98
  }
66
99
 
67
100
  type SyntaxMode = "legacy" | "runes";
@@ -506,7 +539,7 @@ export class ComponentParser {
506
539
  parseSvelteComponent(source: string, diagnostics: ComponentParserDiagnostics): ParsedComponent;
507
540
  }
508
541
 
509
- export type SveldDiagnosticKind = "prop-unknown-type" | "context-any-type" | "slot-missing-type" | "event-no-source" | "example-compile-error" | "example-syntax-error" | "syntax-skipped" | "rest-props-unresolved" | "context-duplicate-key" | "spread-unresolved" | "export-unresolved" | "module-export-conflict" | "extend-props-target-missing" | "extend-props-duplicate" | "extend-props-override" | "jsdoc-unknown-tag" | "typedef-duplicate" | "property-duplicate" | "generics-conflict" | "event-description-ambiguous" | "jsdoc-tag-dropped" | "internal-typedef-referenced" | "types-inline-unresolved";
542
+ export type SveldDiagnosticKind = "prop-unknown-type" | "context-any-type" | "slot-missing-type" | "event-no-source" | "dispatch-escapes" | "example-compile-error" | "example-syntax-error" | "syntax-skipped" | "rest-props-unresolved" | "context-duplicate-key" | "context-key-unresolved" | "spread-unresolved" | "export-unresolved" | "module-export-conflict" | "extend-props-target-missing" | "extend-props-duplicate" | "extend-props-override" | "jsdoc-unknown-tag" | "typedef-duplicate" | "property-duplicate" | "generics-conflict" | "event-description-ambiguous" | "jsdoc-tag-dropped" | "internal-typedef-referenced" | "types-inline-unresolved";
510
543
 
511
544
  type SveldDiagnosticSeverity = "error" | "warning";
512
545