sveld 0.33.0 → 0.34.1

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
@@ -123,6 +123,7 @@ export default class Button extends SvelteComponentTyped<
123
123
  - [Vite](#vite)
124
124
  - [Node.js](#nodejs)
125
125
  - [CLI](#cli)
126
+ - [CI: API-drift checks (`--check`)](#ci-api-drift-checks---check)
126
127
  - [Config File](#config-file)
127
128
  - [Publishing to NPM](#publishing-to-npm)
128
129
  - [Available Options](#available-options)
@@ -202,6 +203,109 @@ Without `resolveTypes`, JSON lists no props. With it, each field shows up with `
202
203
 
203
204
  **Performance.** Off by default. This is the only path that loads TypeScript. It needs `typescript` and a `tsconfig.json`, runs slower than the AST-only pipeline, and gets slower as your types grow. Use it only when you need expanded JSON. `.d.ts` output is unchanged.
204
205
 
206
+ #### Persistent parse cache (`cache`)
207
+
208
+ By default, every component is re-parsed on every run. With `cache`, parsed output is written to disk and reused when the source file has not changed. That applies across runs, including CI on a fresh checkout.
209
+
210
+ ```ts
211
+ await sveld({ json: true, cache: true });
212
+ ```
213
+
214
+ `cache: true` writes to `node_modules/.cache/sveld/parse-cache.json`. Pass a string to use a different location, e.g. `cache: ".cache/sveld.json"`. Also available as `--cache` / `--cache=<path>` on the CLI.
215
+
216
+ If a component [`@extendProps`](#extendprops) / [`@extends`](#extendprops) another file, it is re-parsed when that dependency changes, same as in [`watch`](#available-options) mode. Bumping the `sveld` or Svelte version clears the cache.
217
+
218
+ #### Compile-checked `@example` blocks (`checkExamples`)
219
+
220
+ `@example` blocks are just text. Rename a prop and the sample code can sit there broken for months. Set `checkExamples: true` to run plain TS/JS `@example` blocks through the TypeScript program. Broken examples show up as `example-compile-error` diagnostics.
221
+
222
+ ```ts
223
+ await sveld({ json: true, checkExamples: true });
224
+ ```
225
+
226
+ ```svelte
227
+ <script>
228
+ /**
229
+ * Formats a value.
230
+ * @param {string} value
231
+ * @returns {string}
232
+ * @example
233
+ * ```js
234
+ * formatValue("ok");
235
+ * ```
236
+ */
237
+ export function formatValue(value) {
238
+ return value;
239
+ }
240
+ </script>
241
+ ```
242
+
243
+ If `formatValue` is later renamed and the example is never updated, `checkExamples` reports it:
244
+
245
+ ```
246
+ @example blocks that failed to compile (1):
247
+ ./Component.svelte
248
+ - Line 1: Cannot find name 'formatValue'.
249
+ ```
250
+
251
+ Plain TS/JS only. Examples fenced as `svelte` or `html`, or bare markup like `<Button />`, are skipped. Checking those needs `svelte2tsx` or similar, and sveld stays AST-only.
252
+
253
+ The check is narrow on purpose. It catches renamed or removed symbols and wrong argument counts. It is not full type checking and never pulls in types sveld cannot see.
254
+
255
+ Needs `typescript` and a `tsconfig.json`, same as `resolveTypes`. Use `--strict` (or the `strict` option) to fail CI when an example breaks.
256
+
257
+ #### Type inference diagnostics
258
+
259
+ `sveld` collects unresolved-type diagnostics on every run: props that fall back to `any`, context values typed as `any`, `@event` tags with no dispatch or callback, and (when `checkExamples` is enabled) `example-compile-error`. They are always returned from the programmatic `sveld()` API in `SveldResult.diagnostics`.
260
+
261
+ With `reportDiagnostics` or `strict`, the grouped summary looks like this:
262
+
263
+ ```
264
+ sveld: 4 unresolved types found.
265
+
266
+ Props without inferred types (1):
267
+ ./icons/Add.svelte
268
+ - Prop "title" type could not be inferred; falling back to "any".
269
+
270
+ Context values typed as `any` (1):
271
+ ./ThemeProvider.svelte
272
+ - Context "theme" variable "themeStore" has no type annotation; defaulted to "any".
273
+
274
+ @event tags with no dispatch or callback (2):
275
+ ./Modal.svelte
276
+ - @event "open" has no matching dispatch or callback prop.
277
+ - @event "close" has no matching dispatch or callback prop.
278
+ ```
279
+
280
+ When `checkExamples` is also enabled, `@example` compile failures appear as a fourth group:
281
+
282
+ ```
283
+ @example blocks that failed to compile (1):
284
+ ./Component.svelte
285
+ - Line 1: Cannot find name 'formatValue'.
286
+ ```
287
+
288
+ By default, nothing is printed. Opt in when you are working on types or want CI output:
289
+
290
+ ```ts
291
+ await sveld({ json: true, reportDiagnostics: true });
292
+ ```
293
+
294
+ Use `strict: true` (or `--strict`) to exit with code `1` when diagnostics exist. `strict` implies `reportDiagnostics`, so CI always shows why the run failed.
295
+
296
+ ```ts
297
+ await sveld({ json: true, strict: true });
298
+ ```
299
+
300
+ CLI equivalent:
301
+
302
+ ```sh
303
+ npx sveld --json --report-diagnostics
304
+ npx sveld --json --strict
305
+ ```
306
+
307
+ `--check` is separate: it diffs `COMPONENT_API.json` for API drift and semver classification, not inference warnings.
308
+
205
309
  ## Usage
206
310
 
207
311
  ### Installation
@@ -277,6 +381,32 @@ Generate documentation in JSON and/or Markdown formats using the following flags
277
381
  npx sveld --json --markdown
278
382
  ```
279
383
 
384
+ ### CI: API-drift checks (`--check`)
385
+
386
+ `--check` diffs the parsed component API against a committed `COMPONENT_API.json` snapshot, assigns a semver bump to each change, and exits `1` when it finds a breaking change.
387
+
388
+ 1. Generate and commit the snapshot once: `npx sveld --json`, then commit `COMPONENT_API.json`.
389
+ 2. Add `npx sveld --check` to CI:
390
+
391
+ ```sh
392
+ npx sveld --check
393
+ ```
394
+
395
+ ```
396
+ sveld --check: 2 API changes detected against "COMPONENT_API.json".
397
+ Suggested semver bump: major.
398
+
399
+ Button
400
+ [BREAKING] prop "target" added (required)
401
+ [BREAKING] prop "href" removed
402
+ ```
403
+
404
+ Removed props, events, or slots, and props that become required, are breaking (`major`). New optional props, new events, and widened union types are additive (`minor`). Changes to generics, `@restProps`, `@extends`, or context shapes are not classified further. If any of those changed, `--check` calls it breaking.
405
+
406
+ `--check` does not write the snapshot. Run `sveld --json` (or `sveld --json --check`) and commit the file when you want to update it. If there is no snapshot yet, `--check` prints a notice and exits `0`.
407
+
408
+ Use `--check=<path>` to diff against a snapshot at a custom location (defaults to `jsonOptions.outFile`, or `COMPONENT_API.json`).
409
+
280
410
  ### Node.js
281
411
 
282
412
  You can also call `sveld` from Node.js. The package is **ESM-only**; `require("sveld")` does not work. Use `import` or dynamic `import()`.
@@ -287,7 +417,7 @@ If no `input` is specified, `sveld` will infer the entry point based on the `pac
287
417
  import { sveld } from "sveld";
288
418
  import pkg from "./package.json" with { type: "json" };
289
419
 
290
- sveld({
420
+ const { diagnostics } = await sveld({
291
421
  input: "./src/index.js",
292
422
  glob: true,
293
423
  markdown: true,
@@ -307,6 +437,8 @@ sveld({
307
437
  });
308
438
  ```
309
439
 
440
+ `diagnostics` is always populated; printing is opt-in via `reportDiagnostics` or `strict` (see [Type inference diagnostics](#type-inference-diagnostics)).
441
+
310
442
  #### `jsonOptions.outDir`
311
443
 
312
444
  With `json: true`, `sveld` writes `COMPONENT_API.json` at the project root. The file documents all components.
@@ -381,6 +513,11 @@ TypeScript definitions land in the `types` folder by default. Include that folde
381
513
  - **`markdownOptions`** (object, optional): Options for Markdown output.
382
514
  - **`watch`** (boolean, optional, default: `false`): Regenerate output incrementally when `.svelte` source changes during `vite dev` / `vite build --watch`. Only the changed component and the components that depend on it via [`@extendProps`](#extendprops) / `@extends` are re-parsed, rather than rebuilding every component. Without this option, the plugin only runs during `vite build`.
383
515
  - **`failFast`** (boolean, optional, default: `false`): Abort the entire run when a single component fails to parse. By default, parse failures are collected as diagnostics (and reported to `stderr`) so the remaining components still emit their output. Also available as the `--fail-fast` CLI flag.
516
+ - **`resolveTypes`** (boolean, optional, default: `false`): Load the TypeScript program to expand opaque imported whole-object `$props()` types into JSON. See [Opt-in semantic resolution](#opt-in-semantic-resolution-resolvetypes).
517
+ - **`cache`** (boolean | string, optional, default: `false`): Write parsed component output to disk and skip re-parsing unchanged files on later runs. `true` uses `node_modules/.cache/sveld/parse-cache.json`; a string sets a custom path. Also available as `--cache` / `--cache=<path>`. See [Persistent parse cache](#persistent-parse-cache-cache).
518
+ - **`checkExamples`** (boolean, optional, default: `false`): Run plain TS/JS `@example` blocks through the TypeScript program. Broken ones get an `example-compile-error` diagnostic. See [Compile-checked `@example` blocks](#compile-checked-example-blocks-checkexamples).
519
+ - **`reportDiagnostics`** (boolean, optional, default: `false`): Print unresolved-type diagnostics to stderr (CLI) or `console.warn` (programmatic API). Also available as `--report-diagnostics`. See [Type inference diagnostics](#type-inference-diagnostics).
520
+ - **`strict`** (boolean, optional, default: `false`): Exit with code `1` when diagnostics exist. Implies `reportDiagnostics`. Also available as `--strict`. See [Type inference diagnostics](#type-inference-diagnostics).
384
521
 
385
522
  By default, only TypeScript definitions are generated.
386
523
 
@@ -88,7 +88,7 @@ interface ComponentPropParam {
88
88
  * its type, default value, description, and metadata about whether it's
89
89
  * required, reactive, or a function.
90
90
  */
91
- interface ComponentProp {
91
+ export interface ComponentProp {
92
92
  /** The prop name as declared in the component */
93
93
  name: string;
94
94
  /** The declaration kind: "let" (required), "const" (optional with default), or "function" */
@@ -136,7 +136,7 @@ interface ComponentProp {
136
136
  * Represents a slot that can be used to pass content into the component.
137
137
  * Includes information about slot props, fallback content, and descriptions.
138
138
  */
139
- interface ComponentSlot {
139
+ export interface ComponentSlot {
140
140
  /** The slot name (null or undefined for default slot) */
141
141
  name?: string | null;
142
142
  /** Whether this is the default slot */
@@ -416,6 +416,8 @@ export default class ComponentParser {
416
416
  private readonly typedefs;
417
417
  /** Component generic type parameters (null if no generics) */
418
418
  private generics;
419
+ /** @template tags in a @slot/@snippet block (no @extends), held until finalization. */
420
+ private deferredSlotBlockGenerics;
419
421
  /** Map of prop bindings (e.g., `bind:value`) keyed by prop name */
420
422
  private readonly bindings;
421
423
  /** Map of component contexts (created with `setContext`) keyed by context name */
@@ -1069,6 +1071,7 @@ export default class ComponentParser {
1069
1071
  * parser.parseSvelteComponent(source2, diagnostics2); // Fresh parse
1070
1072
  * ```
1071
1073
  */
1074
+ private accumulateGeneric;
1072
1075
  cleanup(): void;
1073
1076
  /**
1074
1077
  * Pre-compiled regex for matching script blocks in Svelte components.
package/lib/bundle.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { type NormalizedPath } from "./brands";
2
2
  import { type ParsedComponent } from "./ComponentParser";
3
3
  import { type SveldDiagnostic } from "./diagnostics";
4
+ import { ParseCache } from "./parse-cache";
4
5
  import { type EntryExports } from "./parse-entry-exports";
5
6
  import { type ParsedExports } from "./parse-exports";
6
7
  export interface ComponentDocApi extends ParsedComponent {
@@ -45,8 +46,21 @@ export interface GenerateBundleOptions {
45
46
  resolveTypes?: boolean;
46
47
  /** Record consts, functions, and types from the entry barrel. Off by default. */
47
48
  documentExports?: boolean;
49
+ /**
50
+ * Cache parsed component output to disk. Unchanged files skip re-parsing on
51
+ * later runs. `true` uses `node_modules/.cache/sveld/parse-cache.json`; a
52
+ * string sets a custom path. Off by default.
53
+ */
54
+ cache?: boolean | string;
55
+ /**
56
+ * Run plain TS/JS `@example` blocks on props, module exports, slots, and
57
+ * events through the TypeScript program. Broken examples become
58
+ * `example-compile-error` diagnostics. Svelte/HTML markup is skipped.
59
+ * Off by default. Requires `typescript`.
60
+ */
61
+ checkExamples?: boolean;
48
62
  }
49
- export declare function toGenerateBundleOptions(opts?: Pick<GenerateBundleOptions, "failFast" | "resolveTypes" | "documentExports">): GenerateBundleOptions;
63
+ export declare function toGenerateBundleOptions(opts?: Pick<GenerateBundleOptions, "failFast" | "resolveTypes" | "documentExports" | "cache" | "checkExamples">): GenerateBundleOptions;
50
64
  /** A function that resolves a (possibly relative) component path to an absolute path. */
51
65
  export type ResolveComponentFilePath = (filePath: string) => string;
52
66
  /** Options controlling how a single component parse failure is handled. */
@@ -55,6 +69,8 @@ export interface ProcessComponentOptions {
55
69
  failFast?: boolean;
56
70
  /** Invoked with a diagnostic when a component fails to parse (and `failFast` is off). */
57
71
  onParseError?: (error: ComponentParseError) => void;
72
+ /** When set, reuse a component's previous parse if its content hash is unchanged. */
73
+ cache?: ParseCache;
58
74
  }
59
75
  /**
60
76
  * Discovered component sources for an entry point, before parsing.
package/lib/check.d.ts ADDED
@@ -0,0 +1,37 @@
1
+ import type { ComponentDocs } from "./bundle";
2
+ import type { EntryExports } from "./parse-entry-exports";
3
+ import { type ComponentApiDocument } from "./writer/document-model";
4
+ export type SemverBump = "major" | "minor" | "patch" | "none";
5
+ /** One API diff between the committed snapshot and the current parse. */
6
+ export interface ApiChange {
7
+ /** Component `moduleName` this change belongs to, or `"*"` for document-wide notices. */
8
+ component: string;
9
+ kind: "component" | "prop" | "moduleExport" | "event" | "slot" | "shape";
10
+ /** Prop, event, slot, or shape-field name, when applicable. */
11
+ name?: string;
12
+ bump: SemverBump;
13
+ message: string;
14
+ }
15
+ export interface CheckResult {
16
+ /** `false` when there was nothing on disk to diff against (e.g. first run). */
17
+ snapshotExists: boolean;
18
+ snapshotFile: string;
19
+ changes: ApiChange[];
20
+ /** Highest bump across all changes. */
21
+ bump: SemverBump;
22
+ }
23
+ /** Diffs two `COMPONENT_API.json` documents and assigns a semver bump to each change. */
24
+ export declare function diffApiDocuments(previous: ComponentApiDocument, next: ComponentApiDocument): ApiChange[];
25
+ export interface RunCheckOptions {
26
+ /** Entry-barrel exports when `documentExports` is on. */
27
+ entryExports?: EntryExports;
28
+ }
29
+ /**
30
+ * Diffs the current component set against a committed `COMPONENT_API.json`
31
+ * snapshot and assigns a semver bump to each change. Returns
32
+ * `snapshotExists: false` when no snapshot file exists yet, so the first
33
+ * run does not fail CI.
34
+ */
35
+ export declare function runCheck(components: ComponentDocs, snapshotFile: string, options?: RunCheckOptions): Promise<CheckResult>;
36
+ /** Groups changes by component for CLI output. */
37
+ export declare function formatCheckReport(result: CheckResult): string;
package/lib/cli.d.ts CHANGED
@@ -1,8 +1,17 @@
1
1
  import { type PluginSveldOptions } from "./plugin";
2
2
  /** CLI options layered on top of the shared plugin options. */
3
3
  interface CliOptions extends PluginSveldOptions {
4
- /** Set exit code 1 when diagnostics are present. */
4
+ /** Print unresolved-type diagnostics to stderr. */
5
+ reportDiagnostics?: boolean;
6
+ /** Set exit code 1 when diagnostics are present. Implies `reportDiagnostics`. */
5
7
  strict?: boolean;
8
+ /**
9
+ * Diff the parsed component API against a committed snapshot (default:
10
+ * the `json` writer's `outFile`, or `COMPONENT_API.json`) and assign a
11
+ * semver bump to each change. Exits `1` on a breaking change. Pass a
12
+ * string for a custom snapshot path.
13
+ */
14
+ check?: boolean | string;
6
15
  }
7
16
  export declare function parseCliOptions(argv: string[]): CliOptions;
8
17
  /**
@@ -0,0 +1,20 @@
1
+ import type { ComponentDocApi, ComponentDocs, ResolveComponentFilePath } from "./bundle";
2
+ /**
3
+ * Resolves a component's `@extendProps` / `@extends` target to an absolute path,
4
+ * or `null` when it doesn't point at a local `.svelte` file (e.g. it references
5
+ * an external package interface like `carbon-components-svelte`).
6
+ */
7
+ export declare function resolveExtendsDependency(api: ComponentDocApi, componentPath: string): string | null;
8
+ /**
9
+ * Builds a reverse-dependency map: `dependencyPath -> set of dependent paths`.
10
+ *
11
+ * A component is a dependent of `X` when it extends `X` via `@extendProps` /
12
+ * `@extends`. When `X` changes, every dependent must be re-parsed.
13
+ */
14
+ export declare function buildReverseDeps(components: ComponentDocs, resolveComponentFilePath: ResolveComponentFilePath): Map<string, Set<string>>;
15
+ /**
16
+ * Expands the set of changed paths to include every transitive dependent via
17
+ * the reverse-dependency map (e.g. editing `Button.svelte` also marks the
18
+ * `SecondaryButton.svelte` that `@extendProps`-es it).
19
+ */
20
+ export declare function expandAffected(changed: Iterable<string>, reverseDeps: Map<string, Set<string>>): Set<string>;
@@ -4,8 +4,9 @@
4
4
  * - `prop-unknown-type`: prop `typeSource` is `"unknown"`.
5
5
  * - `context-any-type`: `setContext` value inferred as `any`.
6
6
  * - `event-no-source`: `@event` with no dispatch, forward, or callback prop.
7
+ * - `example-compile-error`: an `@example` block failed to type-check (opt-in, `checkExamples`).
7
8
  */
8
- export type SveldDiagnosticKind = "prop-unknown-type" | "context-any-type" | "event-no-source";
9
+ export type SveldDiagnosticKind = "prop-unknown-type" | "context-any-type" | "event-no-source" | "example-compile-error";
9
10
  /**
10
11
  * One place sveld had to guess a type instead of inferring it.
11
12
  */
@@ -0,0 +1,22 @@
1
+ import type { ParsedComponent } from "./ComponentParser";
2
+ /**
3
+ * One `@example` block worth type-checking, reduced to what `resolve-types.ts`
4
+ * needs: a declaration for the documented symbol and the example body itself.
5
+ */
6
+ export interface ExampleCheckSource {
7
+ /** Stable id for diagnostics, e.g. `"prop:variant"` or `"prop:variant#1"` for a second example. */
8
+ id: string;
9
+ /** Human-readable name shown in diagnostics, e.g. `"variant"` or `"variant (example 2)"`. */
10
+ name: string;
11
+ /** TypeScript type to bind the documented symbol to before running `code`. */
12
+ type: string;
13
+ /** The `@example` body, stripped of any surrounding code fence. */
14
+ code: string;
15
+ }
16
+ /**
17
+ * Collects every `@example` block sveld can type-check for a parsed component:
18
+ * plain TS/JS bodies on props, module exports, slots, and events. Svelte/HTML
19
+ * markup examples (most slot/event examples, many prop examples) are skipped.
20
+ * Checking those needs `svelte2tsx` or similar; sveld stays AST-only.
21
+ */
22
+ export declare function collectExampleSources(component: ParsedComponent): ExampleCheckSource[];
package/lib/index.d.ts CHANGED
@@ -1,7 +1,10 @@
1
1
  export { default as ComponentParser, type SerializedComponentEvent } from "./ComponentParser";
2
+ export { type ApiChange, type CheckResult, diffApiDocuments, formatCheckReport, runCheck, type SemverBump, } from "./check";
2
3
  export { cli } from "./cli";
3
4
  export type { SveldDiagnostic, SveldDiagnosticKind } from "./diagnostics";
4
5
  export type { SvelteEntryPoint } from "./get-svelte-entry";
5
6
  export { defineConfig, type SveldConfig } from "./load-config";
6
7
  export { default } from "./plugin";
7
8
  export { sveld } from "./sveld";
9
+ export { buildComponentApiDocument, type ComponentApiDocument } from "./writer/document-model";
10
+ export { getWriter, listWriters, type OutputWriter, registerWriter } from "./writer/registry";