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 diagnostics array while analysis
continues. Covers type resolution failures, member or prop extraction failures, and JSDoc
tag misuse. The result is still valid, and declarations or members with incomplete data are
marked partial: true.
Thrown (fatal): setup-level problems throw from the public entry points:
- a missing or unloadable
tsconfig.json: one that fails to parse, or anextendstarget that doesn't exist, like SvelteKit's generated tsconfig beforesvelte-kit synchas run - Svelte older than 5
- invalid source options: empty
sourcePaths, a path or absolute pattern outsideprojectRoot, orsourcePathsnot undersourceRoot - conflicting options:
discovery: 'exports'withinclude, orresolveImportwithresolveDependencies: false discovery: 'exports'whenexportsis missing or resolves to no files- a name collision under
onDuplicates: 'throw'
Wrap analyze / analyzeFromFiles, or createAnalysisSession and query, to handle these. svelte2tsx
failures don't throw; they surface as transform_failed.
The table below marks each kind's lifecycle. Ingest-time kinds surface when files are ingested and persist with the file until it is re-ingested or deleted, discovery-time comes from file discovery, and unmarked kinds are recomputed on every analysis pass. See the session guide for how they combine.
Shape #
diagnostics is a plain Array<Diagnostic> beside modules in AnalyzeResultJson. It round-trips through JSON.stringify and z.array(Diagnostic).parse. Each entry carries:
kind: discriminant, one per failure mode (see table below)severity:"error"or"warning"file: POSIX-form and relative to the project root, with no leading./; a file outside the project takes the../form. Join withprojectRootto get the absolute path. It names a file, not a module:ModuleJson.pathis relative tosourceRoot, so don't look modules up byfile.line,column: 1-based, optional. Absent when there's no precise position (e.g., a module-level skip). Positions in Svelte files point at the original.sveltesource; one that can't be mapped is omittedmessage: human-readable description, with project-root paths made relative likefile- fields specific to the kind:
symbolName,className,tagName, etc.
Diagnostic kinds #
Severity is fixed per kind: every kind is a warning except transform_failed and module_unreadable, which are always error.
| kind | When it fires |
|---|---|
type_extraction_failed | Trigger: type resolution threw on a declaration or member. Consequence: it's included with partial: true; typeSignature and typeInfo may be absent. |
signature_analysis_failed | Trigger: function or method signature analysis threw. 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. Consequence: that prop's type falls back to "any"; if
the whole props type on $props() is unresolvable, props is
empty. When the acceptsChildren check itself fails
(propName: "children", no position), the type-based detection reports false, though template use of children can still set it. |
legacy_props | Trigger: a component declares props with legacy export let syntax, which is still legal in Svelte 5 but isn't extracted. propNames lists them. Consequence: the component has
empty props; migrate to $props(). |
module_skipped | Trigger: a whole module was skipped during analysis. reason is "not_in_program" (the file wasn't in the
TypeScript program), "no_analyzer" (unsupported extension), or "requires_program" (a Svelte file passed to analyzeModule directly). Consequence: module absent from modules[]. |
module_unreadable | Trigger: a file named by package.json exports couldn't
be read (permission denied, FS error). Consequence: the file is
dropped from the discovered set. Discovery-time. |
import_parse_failed | Trigger: import scanning threw during dependency resolution (not expected: the parse tolerates malformed source). Consequence: the module's dependency edges are dropped; the module still analyzes. Ingest-time. |
duplicate_comment | Trigger: two sources supplied a comment for the same target: an HTML @component comment plus script JSDoc
(commentType: "doc_comment"), or several @module comments
("module_comment"). Consequence: the higher-priority
source wins: script JSDoc for doc comments; instance <script>, then <script module>, then HTML for module comments. |
misplaced_tag | Trigger: a symbol-scope tag (@example, @deprecated, @internal, @since, @see, @throws, @mutates, @default, @nodocs) on a non-primary overload signature, or @nodocs in
a @module comment (then functionName is absent). Consequence: the tag is dropped. Move it to the primary signature; to
omit a module, use exclude patterns. |
unknown_param | Trigger: a @param key matches no parameter (a typo, or a
stale doc after a rename). Consequence: the description is dropped. |
alias_lost | Trigger: TypeScript dropped the name of an exported type alias
(aliasName) whose right-hand side is an indexed access or conditional,
like z.infer<typeof S>, and svelte-docinfo can't recover it.
Literal-only unions (z.enum) and brand intersections don't warn, since
they read fine expanded. Consequence: unannotated positions document
the alias's structure instead of its name. Where applicable, a nominal symbol
(interface Foo extends z.infer<typeof S> {}) restores it. |
duplicate_declaration | Trigger: a declaration name appears in more than one module, and the
opt-in onDuplicates check is on (for packages with a flat export
namespace). declarationName and modules name the conflict; modules holds ModuleJson.path values, unlike file. Consequence: none beyond the diagnostic, unless onDuplicates is 'warn' (logs) or 'throw'. |
transform_failed | Trigger: svelte2tsx threw on a .svelte file. Consequence: the file's ModuleJson is a
placeholder (partial: true, empty declarations). Ingest-time. |
source_map_failed | Trigger: the source map svelte2tsx produced for a .svelte file couldn't be parsed. Consequence: analysis
continues without position mapping: other diagnostics for the file omit line/column, and declaration sourceLines point
into the generated TypeScript. Ingest-time. |
resolver_failed | Trigger: the import resolver threw on specifier (as
opposed to returning null for an external). Consequence: the dependency edge is deferred and retried when a later setFile / setFiles adds files or the importer changes; the diagnostic clears once
it resolves. Ingest-time. |
Use byKind to narrow to one variant with typed fields:
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
declaration or member has incomplete data, from type_extraction_failed, signature_analysis_failed, or class_member_failed, and a ModuleJson is partial when it's a transform_failed placeholder. svelte_prop_failed sets no flag; the prop's type falls back to any. Check partial directly instead of matching diagnostics by
file and line.
Helpers #
Read helpers for the diagnostics array:
- 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 includes diagnostics in its JSON
output, omitted when empty (parse through AnalyzeResultJson to
restore []). Progress messages go to stderr; 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 diagnostics too, so an app can render doc warnings without re-running analysis:
import {modules, diagnostics} from 'virtual:svelte-docinfo';
// the diagnostics subpath keeps the TypeScript compiler out of the client bundle
import {hasErrors} from 'svelte-docinfo/diagnostics.js';
if (hasErrors(diagnostics)) {
// surface in the UI or fail the build
}Absence rule #
Absent optional fields (line, column) are omitted from JSON, per
the compact output rules. The Vite plugin's modules and diagnostics exports are
always present, even when empty. See diagnostics.ts for the Zod schemas and
helpers.