From 68adbc18aeaff2bd130c2376d6bdf72fa38f5e0a Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Mon, 8 May 2023 23:28:29 -0600 Subject: [PATCH] working on types :loading: --- src/runtime/internal/Component.js | 12 ++- .../internal/ResizeObserverSingleton.js | 6 +- src/runtime/internal/animations.js | 2 +- src/runtime/internal/await_block.js | 7 +- src/runtime/internal/dev.d.ts | 94 +++++++++++++++++++ src/runtime/internal/dev.js | 45 ++++----- src/runtime/internal/lifecycle.d.ts | 18 ++++ src/runtime/internal/lifecycle.js | 9 +- src/runtime/internal/loop.js | 3 +- src/runtime/internal/style_manager.js | 2 +- src/runtime/internal/utils.js | 11 ++- src/runtime/store/index.js | 2 +- src/runtime/tsconfig.json | 4 +- 13 files changed, 167 insertions(+), 48 deletions(-) create mode 100644 src/runtime/internal/dev.d.ts create mode 100644 src/runtime/internal/lifecycle.d.ts diff --git a/src/runtime/internal/Component.js b/src/runtime/internal/Component.js index a8f89dca89..4e99d2f2b5 100644 --- a/src/runtime/internal/Component.js +++ b/src/runtime/internal/Component.js @@ -95,7 +95,7 @@ export function init( ) { const parent_component = current_component; set_current_component(component); - /** @type {T$$} */ + /** @type {import('.').T$$} */ const $$ = (component.$$ = { fragment: null, ctx: [], @@ -163,8 +163,11 @@ if (typeof HTMLElement === 'function') { $$connected = false; $$data = {}; $$reflecting = false; + /** @type {Record} */ $$props_definition = {}; + /** @type {Record} */ $$listeners = {}; + /** @type {Map} */ $$listener_unsubscribe_fns = new Map(); constructor($$componentCtor, $$slots, use_shadow_dom) { @@ -310,7 +313,7 @@ if (typeof HTMLElement === 'function') { * @param {string} prop * @param {any} value * @param {Record} props_definition - * @param {'toAttribute' | 'toProp'} transform + * @param {'toAttribute' | 'toProp'} [transform] * @returns {any} */ function get_custom_element_value(prop, value, props_definition, transform) { @@ -349,12 +352,11 @@ function get_custom_element_value(prop, value, props_definition, transform) { * @internal * * Turn a Svelte component into a custom element. - * @param {ComponentType} Component A Svelte component constructor + * @param {import('./dev').ComponentType} Component A Svelte component constructor * @param {Record} props_definition The props to observe * @param {string[]} slots The slots to create * @param {string[]} accessors Other accessors besides the ones for props the component has * @param {boolean} use_shadow_dom Whether to use shadow DOM - * @returns {Class} A custom element class */ export function create_custom_element( Component, @@ -410,7 +412,7 @@ export function create_custom_element( } }); }); - Component.element = Class; + Component.element = /** @type {any} */ (Class); return Class; } diff --git a/src/runtime/internal/ResizeObserverSingleton.js b/src/runtime/internal/ResizeObserverSingleton.js index b4bc03680a..ff71b22457 100644 --- a/src/runtime/internal/ResizeObserverSingleton.js +++ b/src/runtime/internal/ResizeObserverSingleton.js @@ -65,10 +65,10 @@ ResizeObserverSingleton.entries = 'WeakMap' in globals ? new WeakMap() : undefin /** * @typedef {Object} ResizeObserverEntry - * @property {readonlyResizeObserverSize[]} borderBoxSize - * @property {readonlyResizeObserverSize[]} contentBoxSize + * @property {readonly ResizeObserverSize[]} borderBoxSize + * @property {readonly ResizeObserverSize[]} contentBoxSize * @property {DOMRectReadOnly} contentRect - * @property {readonlyResizeObserverSize[]} devicePixelContentBoxSize + * @property {readonly ResizeObserverSize[]} devicePixelContentBoxSize * @property {Element} target */ diff --git a/src/runtime/internal/animations.js b/src/runtime/internal/animations.js index a4424b51a8..a2a42a8551 100644 --- a/src/runtime/internal/animations.js +++ b/src/runtime/internal/animations.js @@ -106,5 +106,5 @@ export function add_transform(node, a) { * node: Element, * { from, to }: { from: PositionRect; to: PositionRect }, * params: any - * ) => AnimationConfig} AnimationFn + * ) => import('../animate').AnimationConfig} AnimationFn */ diff --git a/src/runtime/internal/await_block.js b/src/runtime/internal/await_block.js index c9d5717bb8..739b3c0977 100644 --- a/src/runtime/internal/await_block.js +++ b/src/runtime/internal/await_block.js @@ -4,6 +4,7 @@ import { flush } from './scheduler.js'; import { get_current_component, set_current_component } from './lifecycle.js'; /** + * @template T * @param {Promise} promise * @param {PromiseInfo} info * @returns {boolean} @@ -11,9 +12,10 @@ import { get_current_component, set_current_component } from './lifecycle.js'; export function handle_promise(promise, info) { const token = (info.token = {}); /** - * @param {FragmentFactory} type + * @param {import('.').FragmentFactory} type * @param {0 | 1 | 2} index - * @param {number} key + * @param {number} [key] + * @param {any} [value] * @returns {void} */ function update(type, index, key, value) { @@ -99,6 +101,7 @@ export function update_await_block_branch(info, ctx, dirty) { /** * @typedef {Object} PromiseInfo + * @template T * @property {null|any} ctx * @property {{}} token * @property {boolean} hasCatch diff --git a/src/runtime/internal/dev.d.ts b/src/runtime/internal/dev.d.ts new file mode 100644 index 0000000000..5fcef7a6e3 --- /dev/null +++ b/src/runtime/internal/dev.d.ts @@ -0,0 +1,94 @@ +import type { SvelteComponent } from './Component'; + +export interface SvelteComponentDev< + Props extends Record = any, + Events extends Record = any, + Slots extends Record = any // eslint-disable-line @typescript-eslint/no-unused-vars +> { + $set(props?: Partial): void; + $on>( + type: K, + callback: ((e: Events[K]) => void) | null | undefined + ): () => void; + $destroy(): void; + [accessor: string]: any; +} + +export interface ComponentConstructorOptions< + Props extends Record = Record +> { + target: Element | Document | ShadowRoot; + anchor?: Element; + props?: Props; + context?: Map; + hydrate?: boolean; + intro?: boolean; + $$inline?: boolean; +} + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface SvelteComponentTyped< + Props extends Record = any, + Events extends Record = any, + Slots extends Record = any +> extends SvelteComponentDev {} + +/** + * Convenience type to get the type of a Svelte component. Useful for example in combination with + * dynamic components using ``. + * + * Example: + * ```html + * + * + * + * + * ``` + */ +export type ComponentType = (new ( + options: ComponentConstructorOptions< + Component extends SvelteComponentDev ? Props : Record + > +) => Component) & { + /** The custom element version of the component. Only present if compiled with the `customElement` compiler option */ + element?: typeof HTMLElement; +}; + +/** + * Convenience type to get the props the given component expects. Example: + * ```html + * + * ``` + */ +export type ComponentProps = + Component extends SvelteComponentDev ? Props : never; + +/** + * Convenience type to get the events the given component expects. Example: + * ```html + * + * + * + * ``` + */ +export type ComponentEvents = + Component extends SvelteComponentDev ? Events : never; diff --git a/src/runtime/internal/dev.js b/src/runtime/internal/dev.js index 8b82fb8af0..1d9dbc2ef6 100644 --- a/src/runtime/internal/dev.js +++ b/src/runtime/internal/dev.js @@ -13,6 +13,7 @@ import { is_void } from '../../shared/utils/names.js'; import { contenteditable_truthy_values } from './utils.js'; /** + * @template T * @param {string} type * @param {T} detail * @returns {void} @@ -179,7 +180,7 @@ export function set_data_dev(text, data) { data = '' + data; if (text.data === data) return; dispatch_dev('SvelteDOMSetData', { node: text, data }); - text.data = data; + text.data = /** @type {string} */ (data); } /** @@ -191,7 +192,7 @@ export function set_data_contenteditable_dev(text, data) { data = '' + data; if (text.wholeText === data) return; dispatch_dev('SvelteDOMSetData', { node: text, data }); - text.data = data; + text.data = /** @type {string} */ (data); } /** @@ -293,6 +294,9 @@ export function construct_svelte_component_dev(component, props) { * * * ``` + * @template {Record} Props + * @template {Record} Events + * @template {Record} Slots * @extends SvelteComponent */ export class SvelteComponentDev extends SvelteComponent { @@ -302,6 +306,7 @@ export class SvelteComponentDev extends SvelteComponent { * Does not exist at runtime. * ### DO NOT USE! */ + /** @type {Props} */ $$prop_def = undefined; /** * @private @@ -309,6 +314,7 @@ export class SvelteComponentDev extends SvelteComponent { * Does not exist at runtime. * ### DO NOT USE! */ + /** @type {Events} */ $$events_def = undefined; /** * @private @@ -316,13 +322,16 @@ export class SvelteComponentDev extends SvelteComponent { * Does not exist at runtime. * ### DO NOT USE! */ + /** @type {Slots} */ $$slot_def = undefined; + constructor(options) { if (!options || (!options.target && !options.$$inline)) { throw new Error("'target' is a required option"); } super(); } + /** @returns {void} */ $destroy() { super.$destroy(); @@ -330,16 +339,22 @@ export class SvelteComponentDev extends SvelteComponent { console.warn('Component was already destroyed'); // eslint-disable-line no-console }; } + /** @returns {void} */ $capture_state() {} + /** @returns {void} */ $inject_state() {} } /** + * @template {Record} Props + * @template {Record} Events + * @template {Record} Slots * @deprecated Use `SvelteComponent` instead. See PR for more information: https://github.com/sveltejs/svelte/pull/8512 * @extends SvelteComponentDev */ export class SvelteComponentTyped extends SvelteComponentDev {} + /** @returns {() => void} */ export function loop_guard(timeout) { const start = Date.now(); @@ -349,29 +364,3 @@ export function loop_guard(timeout) { } }; } - -/** - * @typedef {Class} ComponentType - * @template {SvelteComponentDev} [Component=SvelteComponentDev] - */ -/** - * @typedef {Component extends SvelteComponentDev ? Props : never} ComponentProps - * @template {SvelteComponent} Component - */ -/** - * @typedef {Component extends SvelteComponentDev ? Events : never} ComponentEvents - * @template {SvelteComponent} Component - */ - -/** @typedef {Object} SvelteComponentDev */ -/** - * @typedef {Object} ComponentConstructorOptions - * @property {Element|Document|ShadowRoot} target - * @property {Element} [anchor] - * @property {Props} [props] - * @property {Map} [context] - * @property {boolean} [hydrate] - * @property {boolean} [intro] - * @property {boolean} [$$inline] - */ -/** @typedef {Object} SvelteComponentTyped */ diff --git a/src/runtime/internal/lifecycle.d.ts b/src/runtime/internal/lifecycle.d.ts new file mode 100644 index 0000000000..9e49fc4565 --- /dev/null +++ b/src/runtime/internal/lifecycle.d.ts @@ -0,0 +1,18 @@ +export interface EventDispatcher> { + // Implementation notes: + // - undefined extends X instead of X extends undefined makes this work better with both strict and nonstrict mode + // - [X] extends [never] is needed, X extends never would reduce the whole resulting type to never and not to one of the condition outcomes + ( + ...args: [EventMap[Type]] extends [never] + ? [type: Type, parameter?: null | undefined, options?: DispatchOptions] + : null extends EventMap[Type] + ? [type: Type, parameter?: EventMap[Type], options?: DispatchOptions] + : undefined extends EventMap[Type] + ? [type: Type, parameter?: EventMap[Type], options?: DispatchOptions] + : [type: Type, parameter: EventMap[Type], options?: DispatchOptions] + ): boolean; +} + +export interface DispatchOptions { + cancelable?: boolean; +} diff --git a/src/runtime/internal/lifecycle.js b/src/runtime/internal/lifecycle.js index 1e66061ab2..def5586287 100644 --- a/src/runtime/internal/lifecycle.js +++ b/src/runtime/internal/lifecycle.js @@ -36,6 +36,7 @@ export function beforeUpdate(fn) { * `onMount` does not run inside a [server-side component](/docs#run-time-server-side-component-api). * * https://svelte.dev/docs#run-time-svelte-onmount + * @template T * @param {() => T extends Promise<() => any> * ? "Returning a function asynchronously from onMount won't call that function on destroy" * : T} fn @@ -90,7 +91,8 @@ export function onDestroy(fn) { * ``` * * https://svelte.dev/docs#run-time-svelte-createeventdispatcher - * @returns {import("/Users/elliottjohnson/dev/sveltejs/svelte/lifecycle.ts-to-jsdoc").EventDispatcher} + * @template {Record} EventMap + * @returns {import('./lifecycle').EventDispatcher} */ export function createEventDispatcher() { const component = get_current_component(); @@ -99,7 +101,7 @@ export function createEventDispatcher() { if (callbacks) { // TODO are there situations where events could be dispatched // in a server (non-DOM) environment? - const event = custom_event(type, detail, { cancelable }); + const event = custom_event(/** @type {string} */ (type), detail, { cancelable }); callbacks.slice().forEach((fn) => { fn.call(component, event); }); @@ -117,6 +119,7 @@ export function createEventDispatcher() { * Like lifecycle functions, this must be called during component initialisation. * * https://svelte.dev/docs#run-time-svelte-setcontext + * @template T * @param {T} context * @returns {T} */ @@ -130,6 +133,7 @@ export function setContext(key, context) { * Must be called during component initialisation. * * https://svelte.dev/docs#run-time-svelte-getcontext + * @template T * @returns {T} */ export function getContext(key) { @@ -142,6 +146,7 @@ export function getContext(key) { * programmatically create a component and want to pass the existing context to it. * * https://svelte.dev/docs#run-time-svelte-getallcontexts + * @template T * @returns {T} */ export function getAllContexts() { diff --git a/src/runtime/internal/loop.js b/src/runtime/internal/loop.js index dc9f574ea5..67a30e5744 100644 --- a/src/runtime/internal/loop.js +++ b/src/runtime/internal/loop.js @@ -28,7 +28,7 @@ export function clear_loops() { * Creates a new task that runs on each raf frame * until it returns a falsy value or is aborted * @param {TaskCallback} callback - * @returns {import("/Users/elliottjohnson/dev/sveltejs/svelte/loop.ts-to-jsdoc").Task} + * @returns {Task} */ export function loop(callback) { /** @type {TaskEntry} */ @@ -49,5 +49,6 @@ export function loop(callback) { /** * @typedef {Object} Task + * @property {() => void} abort * @property {Promise} promise */ diff --git a/src/runtime/internal/style_manager.js b/src/runtime/internal/style_manager.js index f4767d21c3..bdbd0ec43d 100644 --- a/src/runtime/internal/style_manager.js +++ b/src/runtime/internal/style_manager.js @@ -66,7 +66,7 @@ export function create_rule(node, a, b, duration, delay, ease, fn, uid = 0) { /** * @param {Element & ElementCSSInlineStyle} node - * @param {string} name + * @param {string} [name] * @returns {void} */ export function delete_rule(node, name) { diff --git a/src/runtime/internal/utils.js b/src/runtime/internal/utils.js index e9efcfebaa..4e09fe43eb 100644 --- a/src/runtime/internal/utils.js +++ b/src/runtime/internal/utils.js @@ -4,14 +4,17 @@ export function noop() {} /** @returns {any} */ export const identity = (x) => x; -/** @param {T} tar +/** + * @template T + * @template S + * @param {T} tar * @param {S} src * @returns {T & S} */ export function assign(tar, src) { // @ts-ignore for (const k in src) tar[k] = src[k]; - return tar; + return /** @type {T & S} */ (tar); } // Adapted from https://github.com/then/is-promise/blob/master/index.js @@ -103,7 +106,9 @@ export function subscribe(store, ...callbacks) { return unsub.unsubscribe ? () => unsub.unsubscribe() : unsub; } -/** @param {Readable} store +/** + * @template T + * @param {import('../store').Readable} store * @returns {T} */ export function get_store_value(store) { diff --git a/src/runtime/store/index.js b/src/runtime/store/index.js index e3013e2f8b..08e4c0db8b 100644 --- a/src/runtime/store/index.js +++ b/src/runtime/store/index.js @@ -13,7 +13,7 @@ const subscriber_queue = []; * Creates a `Readable` store that allows reading by subscription. * @param {T} value initial value * @param {StartStopNotifier} start undefined - * @returns {import("/Users/elliottjohnson/dev/sveltejs/svelte/index.ts-to-jsdoc").Readable} + * @returns {Readable} */ export function readable(value, start) { return { diff --git a/src/runtime/tsconfig.json b/src/runtime/tsconfig.json index f3b4691b41..9da06d7d2e 100644 --- a/src/runtime/tsconfig.json +++ b/src/runtime/tsconfig.json @@ -10,6 +10,8 @@ "baseUrl": ".", "paths": { "svelte/*": ["*"] - } + }, + "allowJs": true, + "checkJs": true } }