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 an extends target that doesn't exist, like SvelteKit's generated tsconfig before svelte-kit sync has run
  • Svelte older than 5
  • invalid source options: empty sourcePaths, a path or absolute pattern outside projectRoot, or sourcePaths not under sourceRoot
  • conflicting options: discovery: 'exports' with include, or resolveImport with resolveDependencies: false
  • discovery: 'exports' when exports is 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 with projectRoot to get the absolute path. It names a file, not a module: ModuleJson.path is relative to sourceRoot, so don't look modules up by file.
  • 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 .svelte source; one that can't be mapped is omitted
  • message: human-readable description, with project-root paths made relative like file
  • 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.

kindWhen it fires
type_extraction_failedTrigger: type resolution threw on a declaration or member. Consequence: it's included with partial: true; typeSignature and typeInfo may be absent.
signature_analysis_failedTrigger: function or method signature analysis threw. Consequence: declaration included with partial: true; parameters and overloads may be empty.
class_member_failedTrigger: one member of a class couldn't be analyzed. Consequence: member included with partial: true; siblings still extract normally.
svelte_prop_failedTrigger: 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_propsTrigger: 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_skippedTrigger: 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_unreadableTrigger: 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_failedTrigger: 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_commentTrigger: 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_tagTrigger: 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_paramTrigger: a @param key matches no parameter (a typo, or a stale doc after a rename). Consequence: the description is dropped.
alias_lostTrigger: 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_declarationTrigger: 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_failedTrigger: svelte2tsx threw on a .svelte file. Consequence: the file's ModuleJson is a placeholder (partial: true, empty declarations). Ingest-time.
source_map_failedTrigger: 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_failedTrigger: 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:

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.