diagnostics #
Analysis accumulates errors and warnings without halting. A failing declaration is marked partial: true and the rest of the module still analyzes. Detail lands in an array
of Diagnostic entries, alongside modules in the result.
Two-tier error model #
Accumulated (non-fatal): appended to the Diagnostic array, analysis continues. Covers type resolution failures,
member or prop extraction failures, and JSDoc tag misuse. The return value is still valid but
may carry partial: true on affected declarations.
Thrown (fatal): a small set of setup-level conditions throws from public
entry points: missing tsconfig.json, Svelte <5 detected, or discovery: 'exports' mode with no resolvable exports. Wrap the top-level analyze / analyzeFromFiles call if you want to handle these.
svelte2tsx transformation failures are not thrown; they flow as transform_failed diagnostics.
Shape #
diagnostics is a plain Array<Diagnostic>, no wrapper, no methods.
Round-trips through JSON.stringify / z.array(Diagnostic).parse, so
it serializes alongside modules in CLI output and rehydrates cleanly.
{
modules: ModuleJson[],
diagnostics: Diagnostic[]
} Each Diagnostic carries:
kind: discriminant, one per failure mode (see table below)severity:"error"or"warning"file: POSIX-form, project-relative (no leading./). Rejoin withprojectRootfor absolute paths — including a file outside the project, which takes the../form so the rejoin still recovers it. It names a file, not a module:ModuleJson.pathis relative tosourceRoot, somodules.find((m) => m.path === d.file)is not a valid lookup (that idiom applies to module paths in printed type text, which are a different string).line,column: 1-based, optional. Absent when there's no precise AST node (e.g., a module-level skip). Positions from a Svelte<script module>are remapped to the original.sveltesource; an unmappable position is dropped rather than published as a line in the svelte2tsx outputmessage: human-readable description. Scrubbed of project-root paths and svelte2tsx virtual suffixes likefile, so no diagnostic carries an absolute path.- additional fields specific to the variant:
symbolName,className,tagName, etc.
Diagnostic kinds #
Severity is stable per kind: every kind below is warning severity except transform_failed and module_unreadable, which are always error.
| kind | When it fires |
|---|---|
type_extraction_failed | Trigger: type resolution threw on a symbol. Consequence: declaration included with partial: true and
empty typeSignature. |
signature_analysis_failed | Trigger: function or method signature analysis threw, usually
circular generics or unresolved call signatures. Consequence: declaration included with partial: true;
parameters and overloads may be empty. |
class_member_failed | Trigger: one member of a class couldn't be analyzed. Consequence: member included with partial: true;
siblings still extract normally. |
svelte_prop_failed | Trigger: a Svelte component prop type couldn't be resolved through
the checker. Consequence: per-prop type resolution failures fall back
to "any" for that prop with siblings unaffected; when the whole $props<T>() annotation type is itself unresolvable, the component's props array drops to empty. |
legacy_props | Trigger: a component declares props with legacy export let syntax — still-legal Svelte 5, but prop extraction anchors on $props(), so nothing was extracted. propNames lists the
detected props. Consequence: the component emits with empty props; migrate to $props(). |
module_skipped | Trigger: whole module skipped during the analysis pass. reason narrows to "not_in_program", "no_analyzer", or "requires_program". Consequence: module absent from modules[]. |
module_unreadable | Trigger: file named in package.json exports exists but readFile failed (permission denied, FS error). Consequence: file dropped from the discovered set. Discovery-time. |
import_parse_failed | Trigger: import parsing failed during dependency resolution. Consequence: the dependency edge is dropped; the module itself still analyzes. Ingest-time. |
duplicate_comment | Trigger: two sources supplied a comment for the same target (HTML @component + script JSDoc, or multiple @module comments). commentType narrows to "module_comment" or "doc_comment". Consequence: the higher-priority source
wins: JSDoc for doc comments; instance <script> > <script module> > HTML comment for module comments. |
misplaced_tag | Trigger: symbol-scope tag (@example, @deprecated, @internal, @since, @see, @throws, @mutates, @default, @nodocs) found on a non-primary overload
signature, or @nodocs in a @module comment (where it has no meaning; functionName is absent in that case). Consequence: the tag is dropped; move it to the primary signature —
or, to omit a module from analysis, use exclude patterns. |
unknown_param | Trigger: @param key didn't match any actual parameter
(typo or stale doc after a rename). Consequence: the description is dropped. |
alias_lost | Trigger: an exported type alias's right-hand side (an indexed access
or conditional — z.infer<typeof S> and friends) resolves to a type
the checker keeps no name for, and nothing self-heals: losses the alias registry
recovers are suppressed (their typeInfo emits {kind: 'reference', name} at use sites), as are literal-only
unions (z.enum outputs) and brand intersections — readable degradations
with no author-side fix. aliasName names the alias. Consequence: unannotated positions document the alias's structure
instead of its name; where applicable, an author-side nominal symbol (interface Foo extends z.infer<typeof S> {}) restores it. |
duplicate_declaration | Trigger: a declaration name appears in more than one module, so the
flat-namespace assumption collides. declarationName and modules name the conflict — modules holds ModuleJson.path values, so it is on a different base than this record's file and the two read differently for the same module. Consequence: always emitted; onDuplicates only controls whether to additionally throw, log, or invoke a
callback. |
transform_failed | Trigger: svelte2tsx threw on a .svelte file. Consequence: the file's ModuleJson is synthesized as a placeholder (partial: true, empty declarations). Ingest-time. |
source_map_failed | Trigger: source map parsing failed for a Svelte virtual file. Consequence: analysis continues without position mapping — query-time
diagnostics drop line/column rather than point into the
generated TS, while declaration sourceLines fall back to virtual
positions. Ingest-time. |
resolver_failed | Trigger: import resolver threw on a specifier (vs. legitimately
returning null for externals). specifier names the failing
import. Consequence: the dependency edge is dropped. Ingest-time. |
Each variant is a strict Zod object with its own extra fields. Use byKind to narrow to a specific variant for typed access:
import {byKind} from 'svelte-docinfo';
for (const d of byKind(diagnostics, 'misplaced_tag')) {
// d.tagName, d.functionName, d.file, d.line are typed
console.warn(`${d.functionName}: move @${d.tagName} to the primary overload`);
}severity vs partial #
severity says how loud to be about a problem; partial: true says a specific declaration or member has incomplete data,
typically from type_extraction_failed, signature_analysis_failed, class_member_failed, or svelte_prop_failed. Branch on partial directly; no need to cross-reference diagnostics by file and line.
Helpers #
The diagnostics array is a plain Array<Diagnostic>: construct with [] and mutate with Array.push. Read helpers:
- hasErrors, hasWarnings: boolean checks by severity
- errorsOf, warningsOf: filter by severity
- byKind: filter by kind, narrowed to the matching variant
- formatDiagnostic: format as
'./file.ts:10:5: error: message'(the'./'prefix is fixed)
Consuming diagnostics #
The CLI emits the structured diagnostics field
alongside modules in JSON output — stripped from the wire when empty, so parse
through AnalyzeResultJson to restore the default []. Progress messages go to stderr; the structured diagnostics appear only in
the JSON:
npx svelte-docinfo | jq '.diagnostics | group_by(.kind) | map({kind: .[0].kind, count: length})' Programmatically:
import {analyzeFromFiles, errorsOf, formatDiagnostic, byKind} from 'svelte-docinfo';
const {modules, diagnostics} = await analyzeFromFiles({projectRoot: process.cwd()});
// File paths in diagnostics are already project-relative.
for (const d of errorsOf(diagnostics)) {
console.error(formatDiagnostic(d));
}
// Specific check: any @param typos?
const stale = byKind(diagnostics, 'unknown_param');
if (stale.length) {
console.warn(`${stale.length} stale @param tag(s); fix or remove`);
} The Vite plugin's virtual module exports both modules and diagnostics, so SvelteKit apps can render a
doc-warnings page without re-running analysis:
import {modules, diagnostics} from 'virtual:svelte-docinfo';
import {hasErrors} from 'svelte-docinfo';
if (hasErrors(diagnostics)) {
// surface in the UI or fail the build
}Absence rule #
Optional scalar fields (line, column) drop on serialize per the
same compact-output rules as the rest of the schema. See output format for
the full rule. The Vite plugin's virtual module exposes modules and diagnostics as separate ES module exports, so they're always present even when
empty. See diagnostics.ts for the Zod schemas
and helper signatures.