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 modulemoduleComment: file-level JSDoc comment, if presentdependencies: paths of modules this file importsdependents: paths of modules that import this filestarExports:export * from './module'patternsreExports: same-name re-export edges ({name, module, typeOnly, sourceLine}) in this module's source — the forward view ofalsoExportedFrom(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": addsparameters,returnType,returnDescription,overloads"variable": adds optionaldefaultValue(from@default), plusreactivitywhen the initializer is a Svelte rune ($state,$state.raw,$derived,$derived.by)"class": addsmembers,extends,implements"interface": addsmembers,extends"type": addsmembers,intersects"enum": addsmembers(enum values)"component": addsprops,intersects,acceptsChildren,lang(Svelte components)"snippet": addsparameters(exported Svelte template snippets)"namespace": addsmodule(the source module path projected under this binding); synthesized forexport * as ns from './x'
Shared fields on all variants:
name,kind: identity. Default exports carryname === "default"(see Re-exports below)docComment: JSDoc comment texttypeSignature: full type as a stringsourceLine: line number in the source filemodifiers: e.g.,"readonly","static","getter"genericParams: type parameters with constraints and defaultsexamples,deprecatedMessage,seeAlso,throws,since: from standard JSDoc tagsmutates: from the non-standard@mutatestag, stored asRecord<string, string>mapping target keys to descriptions. Keys are typically parameter names but compound paths (this.foo) and external state references are accepted as-isalsoExportedFrom: modules that re-export this declarationaliasOf: original name if this is a renamed re-exportpartial:truewhen 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
alsoExportedFromarray listing the modules that re-export it. One declaration, multiple import paths. The same edges publish from the re-exporting side asModuleJson.reExports({name, module, typeOnly, sourceLine}, withmodulethe canonical module, multi-hop resolved), so barrels are self-describing without inverting everyalsoExportedFromarray. - Renamed: a synthesized declaration appears in the re-exporting module
with
aliasOf: {module, name}pointing at the canonical. InheritstypeSignature,docComment,parameters,reactivity, anddefaultValuefrom the canonical;sourceLineis the local export specifier's line. - Star exports:
export * from './x'patterns are tracked separately onModuleJson.starExportsand 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 onexternalReExports/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. Addsparameters,returnType,returnDescription,overloads"constructor": class constructors and construct signatures. Addsparameters,overloads"variable": properties, accessors, and index signatures. Adds optionaldefaultValue(from@default), plusreactivityfor 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 typeoptional: whether the prop is optionaldescription: from JSDoc on the propdefaultValue: default value as a string, if presentbindable: set when the prop is declared with the$bindable()rune, so<Foo bind:value />is supported. Modeled here (not via the variable-levelreactivityfield) because$props/$bindableare component-prop concernsparameters: structured parameters for snippet-typed props (e.g.,Snippet<[text: string]>), absent for non-snippet propsexamples,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 stringoptional: whether the parameter has a?tokenrest: whether the parameter uses rest syntax (...args)description: from@paramJSDocdefaultValue: default value expression from the source, if presentpropertyDescriptions: for named object parameters, a record of sub-path → description from dotted@param obj.proptags, if present (destructured params, named__0by 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 stringparameters: parameter list for this overload, with per-overload@paramdescriptionsreturnType: return type for this overload (functions only)genericParams: type parameters for this overloaddocComment: per-overload JSDoc text, if presentreturnDescription: from@returnson 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:extendsconstraint, if presentdefaultType: 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.