output format #
svelte-docinfo outputs JSON describing your project's exported API. Modules contain declarations, and some declarations contain members or props. To see it in practice, jump to the examples.
Top-level structure #
Every surface returns the same envelope, AnalyzeResultJson: the programmatic entry points (analyze, analyzeFromFiles), the CLI's JSON, and the Vite plugin's virtual module.
{
modules: ModuleJson[],
diagnostics: Diagnostic[]
} diagnostics is covered in diagnostics. The CLI emits compact JSON, so an empty project prints {}; parse it through AnalyzeResultJson to
restore the 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'targetsreExports: same-name re-export edges ({name, module, typeOnly, sourceLine}), the forward view ofalsoExportedFrom(see Re-exports)externalReExports: direct re-exports from packages ({name, specifier, originalName?, typeOnly, sourceLine})externalStarExports:export * from 'pkg'specifiers as writtenpartial:truewhen the module is a placeholder for a.sveltefile that svelte2tsx failed to transform (seetransform_failed)
Array fields are omitted from JSON when empty and default to [] after parsing.
DeclarationJson #
Each declaration is a DeclarationJson, a discriminated union on kind with nine variants:
"function": addsparameters,returnType,returnTypeInfo,returnDescription,overloads"variable": adds optionaldefaultValue(from@default) andreactivity(see Reactivity)"class": addsmembers,extends,implements,externalTypes"interface": addsmembers,extends,externalTypes,mergedValue"type": addsmembers,externalTypes,mergedValue"enum": addsmembers(enum values)"component": addsprops,externalTypes,acceptsChildren,lang(Svelte components)"snippet": addsparameters(exported Svelte template snippets)"namespace": addsmodule, the path of the module projected byexport * as ns from './x'
A name declared as both a value and a type, like the schema pattern export const Foo = z.strictObject({...}) + export type Foo = z.infer<typeof Foo>, produces one declaration. The type
meaning documents like its un-merged equivalent (structure, members, typeInfo), and mergedValue: true marks that the name is also a
runtime value, so generateImport emits import instead of import type. JSDoc falls back to the value declaration's comment
when the type has none, and @nodocs on either declaration excludes the pair.
Shared fields on all variants:
name,kind: identity. Default exports carryname === "default"(see Re-exports)docComment: JSDoc comment texttypeSignature: the full type as a string, rendered by the TypeScript checker (see module paths below)typeInfo: a structured TypeJson tree beside the flat string, on variables and type aliases (plus members, component props, parameters, and return types viareturnTypeInfo). See structured types belowsourceLine: 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 tagsinternalMessage: from@internal, a marker rather than an exclusion (empty string for a bare tag)mutates: from@mutates, aRecord<string, string>of target to description (empty for a target with no description). See tagsalsoExportedFrom: modules that re-export this declarationaliasOf: the canonical{module, name}of a renamed re-exportpartial:truewhen extraction failed partway, so the data is incomplete
Declarations tagged @nodocs are excluded from the output and from the opt-in
duplicate-name check.
Module paths in type text #
TypeScript prints a module object as typeof import("…") with an absolute
path, from an import() expression or a typeof over a namespace
import. svelte-docinfo rewrites the path so output never carries one:
- a module in this output is named by its
ModuleJson.path, so the string doubles as a lookup key:modules.find((m) => m.path === s), where a miss means it isn't a module here - a package is named by its path below
node_modules/ - anything else is relative to the project root, with
../sibling/x.tsfor a file outside it
This applies to typeSignature, returnType, member signatures,
and typeInfo text. In a tree, a module object is a terminal {kind: "other"} node carrying that text, never a reference.
Structured types (typeInfo) #
typeInfo is absent when the flat string says everything: intrinsics, bare
references, object and function types. It is present when the tree adds structure: union
and intersection members (alias names kept; enum members as {value, text} pairs with the qualified name as text),
reference name and typeArgs, array element, and
tuple elements (label, ?/... markers, element
type). Arrays and tuples mark readonly. The ten node kinds are intrinsic, literal, reference, array, tuple, union, intersection, function, object, and other.
Object literal and function types stay terminal text. Anything callable is a function node, except a named generic instantiation, which is a reference: Snippet<[a: string]> is a reference whose tuple
type argument carries real elements. An instantiation over the empty tuple
(Snippet<[]>) adds nothing, so it stays absent.
Type aliases relax the absence rule. A union alias's typeSignature prints as
just its own name, so the tree is emitted whatever its shape to carry the members, except
for object and function roots, which members already covers.
Union members follow the order the author wrote them: the alias declaration's
for an aliased union, else the annotation at that position, with null and undefined last. The flat strings print in TypeScript's internal order, which
follows type-creation history, so the two can list the same members in different orders;
read the tree for author intent, such as control options. A union with no written form
(inferred, keyof, mapped or conditional results, generic alias
instantiations) takes the internal order in both.
Lost aliases. TypeScript drops the name of an alias whose right-hand side
is an indexed access or conditional (z.infer<typeof S>, valibot's InferOutput) and expands its structure everywhere it's used. svelte-docinfo
recovers the name as {kind: "reference", name} wherever the checker has
none, from two sources: the type annotation written at that position, and a registry of
the analyzed set's exported lost aliases. The registry also covers unannotated positions:
inferred returns and variables, nested positions, and optional positions typed T | null.
A registry-recovered reference also carries module, the declaring module's ModuleJson.path. It always names a module in the output, so a (module, name) lookup can't dangle; other references never carry it. Recovery
applies at the root too, where the flat string still holds the expansion. Names the
checker has are never overridden, import renames recover the importable name, and two
aliases over one lost type resolve to a single winner everywhere. A loss that can't be
recovered emits an alias_lost warning (see diagnostics).
Re-exports #
Re-exports are encoded by shape:
- Same-name: the canonical declaration's
alsoExportedFromlists the modules that re-export it: one declaration, several import paths. The re-exporting module lists the same edges onModuleJson.reExports, withmodulenaming the canonical module (resolved through chains), so barrels are self-describing. - Renamed: a declaration is synthesized in the re-exporting module with
aliasOf: {module, name}pointing at the canonical. It inherits the canonical's analyzed shape (typeSignature,docComment,parameters, and so on);sourceLineis the local export specifier's line. - Star exports:
export * from './x'lands onModuleJson.starExports, with no per-declaration entries. - Gated canonicals: re-exporting a symbol from a module excluded from
output (e.g.
internal/) synthesizes a full alias even when the name is unchanged, since the canonical module emits nothing to link. ItsaliasOf.modulenames a module absent from output, and noreExportsedge is emitted. - External re-exports: statements that directly name a package
(
export {x} from 'pkg',export * as ns from 'pkg',export * from 'pkg') land onexternalReExports/externalStarExportswith the specifier as written.
A re-export statement with its own JSDoc also synthesizes an alias in the re-exporting
module, even when the name is unchanged, so the local content has somewhere to live. Local
doc fields win; the canonical's fields fill gaps. @nodocs on a re-export
suppresses both the link and the synthesis.
To get a module's complete export surface, call resolveExportSurface(modules, path). It combines
declarations, re-export edges, externals, and star exports with ES semantics (explicit
exports shadow star-projected names, names ambiguous between stars are excluded, default never projects), and reports star targets whose names it can't know.
Default-slot entries carry name === "default", the symbol's
JS-spec name. 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 each module has its own default slot.
Namespace re-exports (export * as ns from './x') produce a "namespace" declaration whose module names the projected source.
Namespaces don't inline members; render ns.a by reading that module's declarations.
MemberJson #
Classes, interfaces, types, and enums list MemberJson entries in members, a discriminated union on kind with three variants:
"function": methods and call signatures. Addsparameters,returnType,returnTypeInfo,returnDescription,overloads, and optionaldefaultValue(from@default, documenting the behavior when a callable option is omitted). A property typed by an external function (run?: typeof spawn) is a"variable"with the flat type text rather than the package's overloads and docs; a mixed callable keeps its local signatures"constructor": class constructors and construct signatures. Addsparameters,overloads"variable": properties, accessors, and index signatures. Adds optionaldefaultValue(from@default),typeInfo, andreactivityfor class fields initialized with a Svelte rune
Nesting is exactly one level deep: members never contain members.
Member name is the source identifier, or a synthesized name where none exists: "constructor" (class constructor), "(construct)" and "(call)" (construct and call signatures on an interface or type alias), and
index-signature names like "[key: string]" (the parameter name as written for
interfaces; a synthesized key for type aliases).
ComponentPropJson #
Component declarations have a props array of ComponentPropJson entries, in source order, followed by any props
an external type declares that the $props() destructuring names, in
destructuring order:
name,type: prop name and TypeScript typetypeInfo: structured TypeJson tree, when it adds structureoptional: whether the prop is optionaldescription: from JSDoc on the propdefaultValue: default value as source text, if presentbindable: set when the prop is declared with$bindable(), so<Foo bind:value />worksparameters: structured parameters for snippet-typed props (e.g.,Snippet<[text: string]>)examples,deprecatedMessage,seeAlso,throws,since,internalMessage,mutates: from the prop's own doc comment. On a prop,mutatesdeclares what the component writes into the object or array it's handed
ParameterJson deliberately lacks those tag fields: a prop is a
named slot with its own documentation, while a parameter's @example, @deprecated, and similar tags belong on the enclosing function per the TSDoc
spec. Parameters get their docs from @param only.
ParameterJson #
Functions, snippets, constructors, and snippet-typed props list ParameterJson entries in parameters:
name: parameter name (e.g.,"options"; a rest parameter's dots live inrest, not the name)type: resolved TypeScript type as a stringtypeInfo: structured TypeJson tree, when it adds structureoptional: whether the parameter has a?tokenrest: whether the parameter uses rest syntax (...args)description: from@paramdefaultValue: default value expression from the source, if presentpropertyDescriptions: sub-path to description, from dotted@param obj.proptags on a named object parameter (destructured parameters aren't covered)
OverloadJson #
Functions and constructors with multiple signatures list OverloadJson entries in overloads. Each carries only
the fields that vary per signature:
typeSignature: the full overload signature as a stringparameters: this overload's parameters, with its own@paramdescriptionsreturnType: return type for this overload (functions only)returnTypeInfo: structured TypeJson tree for the return type, when it adds structuregenericParams: type parameters for this overloaddocComment: per-overload JSDoc text, if presentreturnDescription: from@returnson this overload
Symbol-scope tags (@example, @deprecated, and the rest) live on
the parent declaration only, read from the primary overload. On a non-primary overload they
emit misplaced_tag and are dropped. See Symbol-scope vs signature-scope.
Reactivity #
reactivity appears on VariableDeclarationJson and VariableMemberJson when the initializer is a value-producing
Svelte rune call: $state, $state.raw, $derived, or $derived.by. Detection is syntactic and runs on every file, so a plain .ts file is treated the same as .svelte.ts or a component script.
Only variables and class fields are annotated, not parameters or destructured bindings. $props and $bindable surface on ComponentPropJson instead.
GenericParamJson #
Declarations and members with type parameters list GenericParamJson entries in genericParams:
name: type parameter name (e.g.,"T")constraint:extendsconstraint, if presentdefaultType: default type, if present
Rendering structured types #
Flat type signatures are opaque strings. Where a typeInfo or returnTypeInfo tree exists, flatten it with typeJsonToTokens to render per node: link name tokens to declarations, syntax-highlight code tokens, and print text punctuation as-is.
import {typeJsonToTokens} from 'svelte-docinfo';
typeJsonToTokens(declaration.typeInfo);
// e.g. [{kind: 'name', name: 'Map'}, {kind: 'text', text: '<'},
// {kind: 'code', text: 'string'}, {kind: 'text', text: ', '},
// {kind: 'name', name: 'Tome'}, {kind: 'text', text: '>'}] The tokenizer decides spacing, separators, parentheses, and tuple labels; the renderer decides what each token looks like. typeJsonToText concatenates the same tokens into a plain string.
Compact JSON and absent-as-false #
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 at their default. The value key is exempt, so a {kind: "literal", value: false} type
node survives. Parsing with the Zod schemas from types.ts (or AnalyzeResultJson for the full envelope) restores every default,
so the round-trip is lossless.
Raw-JSON consumers (jq, pipelines that skip .parse()) must treat absent as false: decl.optional === false fails because the
key is gone. Use the schemas, or truthiness checks like if (decl.optional).
Examples #
These show the compact wire form, so empty and default 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]>",
"typeInfo": {
"kind": "reference",
"name": "Snippet",
"typeArgs": [
{
"kind": "tuple",
"elements": [
{"name": "title", "type": {"kind": "intrinsic", "text": "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 math.ts 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 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.