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 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' targets
  • reExports: same-name re-export edges ({name, module, typeOnly, sourceLine}), the forward view of alsoExportedFrom (see Re-exports)
  • externalReExports: direct re-exports from packages ({name, specifier, originalName?, typeOnly, sourceLine})
  • externalStarExports: export * from 'pkg' specifiers as written
  • partial: true when the module is a placeholder for a .svelte file that svelte2tsx failed to transform (see transform_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": adds parameters, returnType, returnTypeInfo, returnDescription, overloads
  • "variable": adds optional defaultValue (from @default) and reactivity (see Reactivity)
  • "class": adds members, extends, implements, externalTypes
  • "interface": adds members, extends, externalTypes, mergedValue
  • "type": adds members, externalTypes, mergedValue
  • "enum": adds members (enum values)
  • "component": adds props, externalTypes, acceptsChildren, lang (Svelte components)
  • "snippet": adds parameters (exported Svelte template snippets)
  • "namespace": adds module, the path of the module projected by export * 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 carry name === "default" (see Re-exports)
  • docComment: JSDoc comment text
  • typeSignature: 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 via returnTypeInfo). See structured types below
  • 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
  • internalMessage: from @internal, a marker rather than an exclusion (empty string for a bare tag)
  • mutates: from @mutates, a Record<string, string> of target to description (empty for a target with no description). See tags
  • alsoExportedFrom: modules that re-export this declaration
  • aliasOf: the canonical {module, name} of a renamed re-export
  • partial: true when 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.ts for 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 alsoExportedFrom lists the modules that re-export it: one declaration, several import paths. The re-exporting module lists the same edges on ModuleJson.reExports, with module naming 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); sourceLine is the local export specifier's line.
  • Star exports: export * from './x' lands on ModuleJson.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. Its aliasOf.module names a module absent from output, and no reExports edge is emitted.
  • External re-exports: statements that directly name a package (export {x} from 'pkg', export * as ns from 'pkg', export * from 'pkg') land on externalReExports / externalStarExports with 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. Adds parameters, returnType, returnTypeInfo, returnDescription, overloads, and optional defaultValue (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. Adds parameters, overloads
  • "variable": properties, accessors, and index signatures. Adds optional defaultValue (from @default), typeInfo, and reactivity for 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 type
  • typeInfo: structured TypeJson tree, when it adds structure
  • optional: whether the prop is optional
  • description: from JSDoc on the prop
  • defaultValue: default value as source text, if present
  • bindable: set when the prop is declared with $bindable(), so <Foo bind:value /> works
  • parameters: 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, mutates declares 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 in rest, not the name)
  • type: resolved TypeScript type as a string
  • typeInfo: structured TypeJson tree, when it adds structure
  • optional: whether the parameter has a ? token
  • rest: whether the parameter uses rest syntax (...args)
  • description: from @param
  • defaultValue: default value expression from the source, if present
  • propertyDescriptions: sub-path to description, from dotted @param obj.prop tags 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 string
  • parameters: this overload's parameters, with its own @param descriptions
  • returnType: return type for this overload (functions only)
  • returnTypeInfo: structured TypeJson tree for the return type, when it adds structure
  • genericParams: type parameters for this overload
  • docComment: per-overload JSDoc text, if present
  • returnDescription: from @returns on 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: extends constraint, if present
  • defaultType: 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.