sveld 0.33.0 → 0.34.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
@@ -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,57 @@ 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
+
205
257
  ## Usage
206
258
 
207
259
  ### Installation
@@ -277,6 +329,32 @@ Generate documentation in JSON and/or Markdown formats using the following flags
277
329
  npx sveld --json --markdown
278
330
  ```
279
331
 
332
+ ### CI: API-drift checks (`--check`)
333
+
334
+ `--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.
335
+
336
+ 1. Generate and commit the snapshot once: `npx sveld --json`, then commit `COMPONENT_API.json`.
337
+ 2. Add `npx sveld --check` to CI:
338
+
339
+ ```sh
340
+ npx sveld --check
341
+ ```
342
+
343
+ ```
344
+ sveld --check: 2 API changes detected against "COMPONENT_API.json".
345
+ Suggested semver bump: major.
346
+
347
+ Button
348
+ [BREAKING] prop "target" added (required)
349
+ [BREAKING] prop "href" removed
350
+ ```
351
+
352
+ 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.
353
+
354
+ `--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`.
355
+
356
+ Use `--check=<path>` to diff against a snapshot at a custom location (defaults to `jsonOptions.outFile`, or `COMPONENT_API.json`).
357
+
280
358
  ### Node.js
281
359
 
282
360
  You can also call `sveld` from Node.js. The package is **ESM-only**; `require("sveld")` does not work. Use `import` or dynamic `import()`.
@@ -381,6 +459,9 @@ TypeScript definitions land in the `types` folder by default. Include that folde
381
459
  - **`markdownOptions`** (object, optional): Options for Markdown output.
382
460
  - **`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
461
  - **`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.
462
+ - **`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).
463
+ - **`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).
464
+ - **`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).
384
465
 
385
466
  By default, only TypeScript definitions are generated.
386
467
 
@@ -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
@@ -3,6 +3,13 @@ import { type PluginSveldOptions } from "./plugin";
3
3
  interface CliOptions extends PluginSveldOptions {
4
4
  /** Set exit code 1 when diagnostics are present. */
5
5
  strict?: boolean;
6
+ /**
7
+ * Diff the parsed component API against a committed snapshot (default:
8
+ * the `json` writer's `outFile`, or `COMPONENT_API.json`) and assign a
9
+ * semver bump to each change. Exits `1` on a breaking change. Pass a
10
+ * string for a custom snapshot path.
11
+ */
12
+ check?: boolean | string;
6
13
  }
7
14
  export declare function parseCliOptions(argv: string[]): CliOptions;
8
15
  /**
@@ -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";