svelte-legacy.ts

Legacy (runes-less) Svelte component support: props declared with export let instead of $props().

Still-legal syntax under Svelte 5, with no $props() declaration for prop extraction to anchor on. This module supplies the two things that replace it, and nothing else: a syntactic scan of the original instance script (scanLegacyExports) and the checker anchor in the svelte2tsx virtual (resolveLegacyPropsAnchor). analyzeSvelteModule hands the result to the same prop extraction the runes path uses, so filtering, types, callbacks, and JSDoc are shared.

What a legacy component documents: its props, all of them bindable (any export let can be bound). Slots and events aren't modeled here; a default slot sets acceptsChildren in svelte.ts, which covers legacy components with no props too.

@internal

view source

Declarations
#

5 declarations

LegacyExports
#

svelte-legacy.ts view source

LegacyExports import type {LegacyExports} from 'svelte-docinfo/svelte-legacy.js';

What scanLegacyExports reads from an instance script.

props

Props, in source order of their exports.

type LegacyProp[]

accessors

Exported names of accessors: export const, export function, export class, and export clauses over those bindings. svelte2tsx lists them in the props type as optional properties, but they're read from the component instance, never passed in.

type Set<string>

LegacyProp
#

svelte-legacy.ts view source

LegacyProp import type {LegacyProp} from 'svelte-docinfo/svelte-legacy.js';

A prop found by scanLegacyExports.

name

Exported prop name (the rename in the export {a as b} form).

type string

pos

Offset of the export within the script content; the caller owns line mapping.

type number

hasInitializer

Whether the binding has an initializer, which makes the prop optional.

type boolean

defaultValue?

Verbatim source text of the initializer. Absent without one, and for an explicit undefined, which says the same as no default.

type string

LegacyPropsAnchor
#

svelte-legacy.ts view source

LegacyPropsAnchor import type {LegacyPropsAnchor} from 'svelte-docinfo/svelte-legacy.js';

The checker anchor for a legacy component's props, resolved by resolveLegacyPropsAnchor.

type

The props type.

type Type

node

The node the type was read from, in the component's own virtual: the written type (a TS component) or the props object literal (a JS one, which has no type node).

type Node

kind

Which props value shape node was read from:

  • 'untyped' — a bare object literal (a JS component, and a TS one whose exports are all untyped and uninitialized). Every property reads as required, so optionality comes from the script instead (LegacyProp.hasInitializer). Only the scanned prop names are props.
  • 'generated' — a type svelte2tsx wrote from the exports, whose properties carry their own optionality. It lists every export, accessors and type-only exports included, so only the scanned prop names are props.
  • 'authored' — the author's own $$Props, whose properties carry their own optionality. It can declare props the script never exports, so all of its properties are props except the accessors, which svelte2tsx intersects in beside it.

type "untyped" | "generated" | "authored"

resolveLegacyPropsAnchor
#

svelte-legacy.ts view source

(propsValue: Expression | undefined, checker: TypeChecker): LegacyPropsAnchor | undefined import {resolveLegacyPropsAnchor} from 'svelte-docinfo/svelte-legacy.js';

Resolve the props anchor of a legacy component from its svelte2tsx virtual.

svelte2tsx ends $$render with return {props: …, slots: …, …}, and the props value is the anchor. The shapes recognized:

  • Typed — <object> as <Type>. The type is a literal svelte2tsx writes from the exports, carrying each prop's JSDoc and optionality ({a?: typeof a, b: number}: 'generated'), or the author's reserved $$Props, bare or intersected with a literal of the accessors ('authored').
  • Untyped — a bare object literal ({a: a, b: b}: 'untyped'), what a JS component gets, and a TS one whose exports are all untyped and uninitialized, with each binding's JSDoc copied onto its property, so a @type there types the prop.

Anything else is undefined: an unknown virtual yields no props rather than guessed ones, and the caller reports it.

A props value svelte2tsx built from no exports is unrecognized here too: an empty object under a cast ({} as Record<string, never>, or in JS a JSDoc cast on ({})). The caller asks only when the scan found exports, so an empty value means svelte2tsx didn't read the script the scan read, and the exports are reported, not dropped in silence.

Whether the component is legacy at all isn't decided here. The caller asks only for a component svelte2tsx read as legacy (classifyRenderMode in svelte.ts); one it read as runes has an empty props type that would be unrecognized here, and its exported bindings are instance exports.

The props are what the type declares, so an export let name an authored $$Props leaves out is no prop: a typed consumer can't pass it.

Where svelte2tsx's props value departs from Svelte, the output follows it:

  • A binding exported both directly and through a renaming clause (export let a; export {a as b}) keeps only the renamed prop, without the binding's JSDoc. In TS it reads as optional.
  • An export clause written above its binding loses the binding's JSDoc too, and in TS reads as optional.

propsValue

the props value of the object $$render returns, when the virtual has one

type Expression | undefined

checker

type TypeChecker

returns

LegacyPropsAnchor | undefined

the anchor, or undefined when the value's shape isn't recognized or the checker throws

scanLegacyExports
#

svelte-legacy.ts view source

(scriptSource: SourceFile): LegacyExports import {scanLegacyExports} from 'svelte-docinfo/svelte-legacy.js';

Scan an instance script for legacy props and accessors.

Runes-less components declare props by exporting mutable bindings — export let a / export var a directly, or let a; export {a as b} for renames (the reserved-word workaround). export const / export function / export class are accessors, not props, and type-only exports aren't runtime bindings.

The scan says what the script exports, not what mode the component is in: a runes component can export a mutable binding too, as an instance export. The caller scans only a component svelte2tsx didn't read as runes (classifyRenderMode in svelte.ts).

The scan reads the *original* instance script because svelte2tsx rewrites the export modifier away inside $$render; a purely syntactic walk over top-level statements is all it needs. It's also where defaults come from: defaultValue is the initializer's source text as the author wrote it.

scriptSource

parsed instance <script> content from the original source

type SourceFile

returns

LegacyExports

props in source order (empty when the script exports no mutable binding) and accessor names

Imported by
#