output format #

svelte-docinfo outputs JSON describing your project's exported API. The data format is a hierarchy: modules contain declarations, and some declarations contain members or props. You may want to skip the explanation and jump down to the examples.

Top-level structure
#

Programmatic entry points (analyze, analyzeFromFiles) return both modules and accumulated diagnostics:

{ modules: ModuleJson[], diagnostics: Diagnostic[] }

All surfaces emit this shape. The CLI's stdout JSON and the Vite plugin's virtual module both expose modules and diagnostics (matching AnalyzeResultJson). The CLI runs output through compactReplacer so empty arrays strip on the wire (an empty-project run emits {}); parse JSON consumers through AnalyzeResultJson to restore Zod defaults.

ModuleJson
#

A ModuleJson describes a single source file and its exports:

  • path: file path relative to the source root (e.g., "math.ts")
  • declarations: exported items from this module
  • moduleComment: file-level JSDoc comment, if present
  • dependencies: paths of modules this file imports
  • dependents: paths of modules that import this file
  • starExports: export * from './module' patterns
  • reExports: same-name re-export edges ({name, module, typeOnly, sourceLine}) in this module's source — the forward view of alsoExportedFrom (see Re-exports below)
  • externalReExports: direct re-exports from packages ({name, specifier, originalName?, typeOnly, sourceLine})
  • externalStarExports: export * from 'pkg' specifiers as written

Array fields (declarations, dependencies, etc.) are omitted from JSON when empty and default to [] at runtime after parsing.

DeclarationJson
#

Each declaration is a DeclarationJson, a discriminated union on the kind field with nine variants:

  • "function": adds parameters, returnType, returnDescription, overloads
  • "variable": adds optional defaultValue (from @default), plus reactivity when the initializer is a Svelte rune ($state, $state.raw, $derived, $derived.by)
  • "class": adds members, extends, implements
  • "interface": adds members, extends
  • "type": adds members, intersects
  • "enum": adds members (enum values)
  • "component": adds props, intersects, acceptsChildren, lang (Svelte components)
  • "snippet": adds parameters (exported Svelte template snippets)
  • "namespace": adds module (the source module path projected under this binding); synthesized for export * as ns from './x'

Shared fields on all variants:

  • name, kind: identity. Default exports carry name === "default" (see Re-exports below)
  • docComment: JSDoc comment text
  • typeSignature: full type as a string
  • sourceLine: line number in the source file
  • modifiers: e.g., "readonly", "static", "getter"
  • genericParams: type parameters with constraints and defaults
  • examples, deprecatedMessage, seeAlso, throws, since: from standard JSDoc tags
  • mutates: from the non-standard @mutates tag, stored as Record<string, string> mapping target keys to descriptions. Keys are typically parameter names but compound paths (this.foo) and external state references are accepted as-is
  • alsoExportedFrom: modules that re-export this declaration
  • aliasOf: original name if this is a renamed re-export
  • partial: true when extraction failed partway through the declaration, indicating incomplete data

Declarations tagged with @nodocs are excluded from the output entirely and are also excluded from duplicate name checking.

Re-exports
#

Re-exports are encoded with two shapes, chosen by content:

  • Same-name: the canonical declaration carries an alsoExportedFrom array listing the modules that re-export it. One declaration, multiple import paths. The same edges publish from the re-exporting side as ModuleJson.reExports ({name, module, typeOnly, sourceLine}, with module the canonical module, multi-hop resolved), so barrels are self-describing without inverting every alsoExportedFrom array.
  • Renamed: a synthesized declaration appears in the re-exporting module with aliasOf: {module, name} pointing at the canonical. Inherits typeSignature, docComment, parameters, reactivity, and defaultValue from the canonical; sourceLine is the local export specifier's line.
  • Star exports: export * from './x' patterns are tracked separately on ModuleJson.starExports and don't synthesize per-declaration entries.
  • External re-exports: statements directly referencing a package (export {x} from 'pkg', export * as ns from 'pkg', export * from 'pkg') land on externalReExports / externalStarExports — flat statement facts with the specifier as written, no canonical to resolve.

When a re-export statement carries its own JSDoc or @nodocs, an alias is also synthesized in the re-exporting module so the local content has somewhere to live, even when the name is unchanged. The trigger is "presence of local content," not "presence of rename." Local doc-comment fields apply first and stick; canonical fields only fill gaps. @nodocs on a re-export suppresses both the link and the synthesis.

To compute a module's complete export surface from these encodings, use resolveExportSurface(modules, path) — it combines declarations, re-export edges, externals, and transitively-resolved star exports with ES semantics (explicit exports shadow star-projected names, names ambiguous between stars are excluded, default never projects), and reports unresolved or external star targets whose names it can't know.

Default-slot entries carry name === "default" (see the shared-fields note above for why). Renames out of the default slot (export {default as Foo} from './x') carry name: "Foo" and aliasOf: {module, name: "default"}. Duplicate-name checks skip "default" since the default slot is module-scoped per the JS spec.

Namespace re-exports (export * as ns from './x') synthesize a NamespaceDeclarationJson with module pointing at the source the namespace projects. Consumers render ns.a / ns.b by reading the source module's declarations; namespaces don't inline members.

MemberJson
#

Classes, interfaces, types, and enums can contain MemberJson entries in their members arrays. MemberJson is a discriminated union on kind with three variants:

  • "function": methods and call signatures. Adds parameters, returnType, returnDescription, overloads
  • "constructor": class constructors and construct signatures. Adds parameters, overloads
  • "variable": properties, accessors, and index signatures. Adds optional defaultValue (from @default), plus reactivity for class fields initialized with a Svelte rune

Member kind is restricted to these three variants. Nesting is exactly one level deep: members never contain their own members.

Member name is the user-chosen identifier in most cases, but synthesized names appear when no source identifier exists: "constructor" (class constructor), "(construct)" (construct signature on an interface or type alias), "(call)" (call signature on an interface or type alias), and bracketed index-signature names like "[key: string]" (the parameter name and key type as written for interfaces; a synthesized key for type aliases).

ComponentPropJson
#

Component declarations have a props array of ComponentPropJson entries:

  • name, type: prop name and TypeScript type
  • optional: whether the prop is optional
  • description: from JSDoc on the prop
  • defaultValue: default value as a string, if present
  • bindable: set when the prop is declared with the $bindable() rune, so <Foo bind:value /> is supported. Modeled here (not via the variable-level reactivity field) because $props/$bindable are component-prop concerns
  • parameters: structured parameters for snippet-typed props (e.g., Snippet<[text: string]>), absent for non-snippet props
  • examples, deprecatedMessage, seeAlso, throws, since: symbol-scope JSDoc tags parsed from the prop's own doc comment (same shape as the declaration shared fields)

Asymmetry with ParameterJson. Props carry the symbol-scope tag fields above; function parameters deliberately don't. A prop is a named slot with its own documentation surface. A parameter is positional, and its @example/@deprecated/@since/@see/@throws belong on the enclosing function symbol per the TSDoc spec. Per-parameter content lives on ParameterJson.description (and propertyDescriptions for object-property docs) from @param only.

ParameterJson
#

Functions, snippets, constructors, and snippet-typed component props use ParameterJson entries in their parameters arrays:

  • name: parameter name (e.g., "options", "...args")
  • type: resolved TypeScript type as a string
  • optional: whether the parameter has a ? token
  • rest: whether the parameter uses rest syntax (...args)
  • description: from @param JSDoc
  • defaultValue: default value expression from the source, if present
  • propertyDescriptions: for named object parameters, a record of sub-path → description from dotted @param obj.prop tags, if present (destructured params, named __0 by TypeScript, are not covered)

OverloadJson
#

Functions and constructors with multiple signatures use OverloadJson entries in their overloads arrays. Each overload captures only signature-scope content, the fields that can vary meaningfully per signature:

  • typeSignature: the full overload signature as a string
  • parameters: parameter list for this overload, with per-overload @param descriptions
  • returnType: return type for this overload (functions only)
  • genericParams: type parameters for this overload
  • docComment: per-overload JSDoc text, if present
  • returnDescription: from @returns on this overload

Symbol-scope JSDoc tags (@example, @deprecated, @since, @see, @throws, @mutates) describe the function as a whole and live on the parent declaration only, not duplicated per overload. The primary overload's JSDoc feeds the parent's symbol-level extraction; placing one of those tags on a non-primary overload signature emits a misplaced_tag warning and the tag is dropped (no synthetic content, no silent loss). Typo'd or stale @param keys produce unknown_param warnings the same way.

Reactivity
#

The reactivity field appears on VariableDeclarationJson and VariableMemberJson when the initializer is a value-producing Svelte rune call: $state, $state.raw, $derived, or $derived.by. Detection is purely syntactic and runs on every analyzed file regardless of extension, capturing the same patterns in a plain .ts file as in .svelte.ts or a component's <script>.

It covers variables (top-level and class fields). Function parameters and destructured bindings are not annotated even when the value flows from a rune. $props and $bindable are component-prop concerns and surface on ComponentPropJson's bindable field instead.

GenericParamJson
#

Declarations and members with type parameters use GenericParamJson entries in their genericParams arrays:

  • name: type parameter name (e.g., "T")
  • constraint: extends constraint, if present
  • defaultType: default type, if present

Working with type strings
#

Type signatures are opaque strings produced by the TypeScript compiler. To discover which in-project declaration names appear in a type string (e.g., for rendering clickable links), use findTypeReferences:

import {findTypeReferences} from 'svelte-docinfo'; const names = new Set(modules.flatMap(m => m.declarations.map(d => d.name))); findTypeReferences('Map<string, ModuleJson[]>', names); // => ['ModuleJson']

When scanning many type strings against the same set of names, pre-compile the patterns with buildTypeReferencePatterns to avoid recompiling regexes on every call.

Compact JSON and absent-as-false
#

By default, output uses compact JSON via compactReplacer: empty arrays, false booleans, and undefined fields are stripped, so optional, acceptsChildren, partial, rest, bindable, and similar fields vanish from the wire form when their value is the default. After parsing with the Zod schemas from types.ts (or AnalyzeResultJson for the full {modules, diagnostics} envelope), all defaults are restored, and the round-trip is lossless.

Raw-JSON consumers (e.g., jq, hand-rolled pipelines that skip .parse()) must treat absent as false; a literal decl.optional === false check silently fails because the key is gone. Use the schemas, or truthy/falsy checks (if (decl.optional) …) on raw JSON.

Examples
#

The JSON output for these examples is the compact wire form described above, so empty arrays, false booleans, and absent optional fields are stripped.

A TypeScript function in math.ts:

/** Clamp a number to a range. */ export const clamp = (value: number, min: number, max: number): number => Math.min(Math.max(value, min), max); { "modules": [ { "path": "math.ts", "declarations": [ { "name": "clamp", "kind": "function", "docComment": "Clamp a number to a range.", "typeSignature": "(value: number, min: number, max: number): number", "sourceLine": 2, "parameters": [ {"name": "value", "type": "number"}, {"name": "min", "type": "number"}, {"name": "max", "type": "number"} ], "returnType": "number" } ] } ] }

A Svelte component Card.svelte with a snippet prop, children, and an exported snippet:

<!-- @component A card with a customizable header. --> <script lang="ts" module> /** Default footer snippet. */ export {card_footer}; </script> <script lang="ts"> import type {Snippet} from 'svelte'; const { title, header, children, }: { title: string; /** Custom header rendering. */ header?: Snippet<[title: string]>; children?: Snippet; } = $props(); </script> <div class="card"> {#if header}{@render header(title)}{:else}<h2>{title}</h2>{/if} {@render children?.()} </div> {#snippet card_footer(text: string)} <small>{text}</small> {/snippet} { "modules": [ { "path": "Card.svelte", "declarations": [ { "name": "Card", "kind": "component", "docComment": "A card with a customizable header.", "sourceLine": 7, "props": [ {"name": "title", "type": "string"}, { "name": "header", "type": "Snippet<[title: string]>", "optional": true, "description": "Custom header rendering.", "parameters": [ {"name": "title", "type": "string"} ] }, {"name": "children", "type": "Snippet<[]>", "optional": true} ], "acceptsChildren": true }, { "name": "card_footer", "kind": "snippet", "docComment": "Default footer snippet.", "typeSignature": "Snippet<[text: string]>", "sourceLine": 27, "parameters": [ {"name": "text", "type": "string"} ] } ] } ] }

A rune module counter.svelte.ts exporting reactive state:

export let count = $state(0); export const doubled = $derived(count * 2); { "modules": [ { "path": "counter.svelte.ts", "declarations": [ { "name": "count", "kind": "variable", "typeSignature": "number", "sourceLine": 1, "reactivity": "$state" }, { "name": "doubled", "kind": "variable", "typeSignature": "number", "sourceLine": 2, "reactivity": "$derived" } ] } ] }

A barrel index.ts that re-exports clamp from the math.ts above under a new name and star-exports other.ts. The rename synthesizes a declaration with aliasOf that inherits the canonical's signature and docs, the star export lands on starExports, and the dependency graph fields connect the modules. (A same-name re-export would instead add the barrel to the canonical's alsoExportedFrom.)

// index.ts export {clamp as clampNumber} from './math.js'; export * from './other.js'; // other.ts export const TAU = Math.PI * 2; { "modules": [ { "path": "index.ts", "declarations": [ { "name": "clampNumber", "kind": "function", "docComment": "Clamp a number to a range.", "typeSignature": "(value: number, min: number, max: number): number", "sourceLine": 1, "aliasOf": {"module": "math.ts", "name": "clamp"}, "parameters": [ {"name": "value", "type": "number"}, {"name": "min", "type": "number"}, {"name": "max", "type": "number"} ], "returnType": "number" } ], "dependencies": ["math.ts", "other.ts"], "starExports": ["other.ts"] }, { "path": "math.ts", "declarations": [ { "name": "clamp", "kind": "function", "docComment": "Clamp a number to a range.", "typeSignature": "(value: number, min: number, max: number): number", "sourceLine": 2, "parameters": [ {"name": "value", "type": "number"}, {"name": "min", "type": "number"}, {"name": "max", "type": "number"} ], "returnType": "number" } ], "dependents": ["index.ts"] }, { "path": "other.ts", "declarations": [ {"name": "TAU", "kind": "variable", "typeSignature": "number", "sourceLine": 1} ], "dependents": ["index.ts"] } ] }

See the types module for the full Zod schemas, and the API reference for all exported types.