From c17384b834e5ea390a4c19d47fb97770ac7eb7fa Mon Sep 17 00:00:00 2001 From: Ivan Hofer Date: Fri, 4 Feb 2022 17:04:39 +0100 Subject: [PATCH 01/13] improve types for `createEventDispatcher` --- src/runtime/internal/lifecycle.ts | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/src/runtime/internal/lifecycle.ts b/src/runtime/internal/lifecycle.ts index bb3df3d29526..b188111ba28b 100644 --- a/src/runtime/internal/lifecycle.ts +++ b/src/runtime/internal/lifecycle.ts @@ -27,12 +27,29 @@ export function onDestroy(fn: () => any) { get_current_component().$$.on_destroy.push(fn); } +type UnionToIntersection = (U extends any ? (k: U) => void : never) extends (k: infer I) => void + ? I + : never + +type ExtractObjectValues> = Object[keyof Object] + +type ConstructDispatchFunction, EventKey extends keyof EventMap> = + EventMap[EventKey] extends never + ? (type: EventKey) => void + : (type: EventKey, detail: EventMap[EventKey]) => void + +type CreateDispatchFunctionMap = { + [Key in keyof EventMap]: ConstructDispatchFunction +} + +type EventDispatcher> = UnionToIntersection>> + export function createEventDispatcher< - EventMap extends {} = any ->(): >(type: EventKey, detail?: EventMap[EventKey]) => void { + EventMap extends Record = any +>(): EventDispatcher { const component = get_current_component(); - return (type: string, detail?: any) => { + return ((type: string, detail?: any) => { const callbacks = component.$$.callbacks[type]; if (callbacks) { @@ -43,7 +60,7 @@ export function createEventDispatcher< fn.call(component, event); }); } - }; + }) as EventDispatcher; } export function setContext(key, context: T) { @@ -59,7 +76,7 @@ export function getAllContexts = Map>(): T { } export function hasContext(key): boolean { - return get_current_component().$$.context.has(key); + return get_current_component().$$.context.has(key); } // TODO figure out if we still want to support From 22aa51d149acef0291a9ecd1dd6342aefa6b3c1d Mon Sep 17 00:00:00 2001 From: Ivan Hofer Date: Fri, 4 Feb 2022 19:25:15 +0100 Subject: [PATCH 02/13] allow optional parameters for `createEventDispatcher` --- src/runtime/internal/lifecycle.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/runtime/internal/lifecycle.ts b/src/runtime/internal/lifecycle.ts index b188111ba28b..faaeac2988eb 100644 --- a/src/runtime/internal/lifecycle.ts +++ b/src/runtime/internal/lifecycle.ts @@ -36,6 +36,8 @@ type ExtractObjectValues> = Object[keyof Object] type ConstructDispatchFunction, EventKey extends keyof EventMap> = EventMap[EventKey] extends never ? (type: EventKey) => void + : undefined extends EventMap[EventKey] + ? (type: EventKey, detail?: EventMap[EventKey]) => void : (type: EventKey, detail: EventMap[EventKey]) => void type CreateDispatchFunctionMap = { From 54918719681fcff1ccb17faf889691efdef8dc38 Mon Sep 17 00:00:00 2001 From: Ivan Hofer Date: Sat, 5 Feb 2022 09:52:56 +0100 Subject: [PATCH 03/13] replace `undefined` with `null` --- src/runtime/internal/lifecycle.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/runtime/internal/lifecycle.ts b/src/runtime/internal/lifecycle.ts index faaeac2988eb..4eacb698d62b 100644 --- a/src/runtime/internal/lifecycle.ts +++ b/src/runtime/internal/lifecycle.ts @@ -34,9 +34,9 @@ type UnionToIntersection = (U extends any ? (k: U) => void : never) extends ( type ExtractObjectValues> = Object[keyof Object] type ConstructDispatchFunction, EventKey extends keyof EventMap> = - EventMap[EventKey] extends never + EventMap[EventKey] extends never | null ? (type: EventKey) => void - : undefined extends EventMap[EventKey] + : null extends EventMap[EventKey] ? (type: EventKey, detail?: EventMap[EventKey]) => void : (type: EventKey, detail: EventMap[EventKey]) => void From d2245664350e530aae0e62fc87a6256ac3b925a2 Mon Sep 17 00:00:00 2001 From: Ivan Hofer Date: Sat, 12 Feb 2022 09:15:03 +0100 Subject: [PATCH 04/13] add types tests --- package.json | 3 +- .../runtime/internal/lifecyle.test-types.ts | 33 +++++++++++++++++++ test/types/tsconfig.json | 28 ++++++++++++++++ 3 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 test/types/runtime/internal/lifecyle.test-types.ts create mode 100644 test/types/tsconfig.json diff --git a/package.json b/package.json index 2ca79c5fa897..526cdae9e10d 100644 --- a/package.json +++ b/package.json @@ -83,8 +83,9 @@ }, "types": "types/runtime/index.d.ts", "scripts": { - "test": "mocha --exit", + "test": "mocha --exit && npm run test:types", "test:unit": "mocha --require sucrase/register --recursive src/**/__test__.ts --exit", + "test:types": "tsc --project test/types/tsconfig.json --noEmit", "quicktest": "mocha", "build": "rollup -c && npm run tsd", "prepare": "npm run build", diff --git a/test/types/runtime/internal/lifecyle.test-types.ts b/test/types/runtime/internal/lifecyle.test-types.ts new file mode 100644 index 000000000000..6190f675a597 --- /dev/null +++ b/test/types/runtime/internal/lifecyle.test-types.ts @@ -0,0 +1,33 @@ +import { createEventDispatcher } from '$runtime/internal/lifecycle'; + +const dispatch = createEventDispatcher<{ + loaded: never + change: string + valid: boolean + optional: number | null +}>(); + +// @ts-expect-error: dispatch invalid event +dispatch('some-event'); + +dispatch('loaded'); +// @ts-expect-error: no detail accepted +dispatch('loaded', 123); + +// @ts-expect-error: detail not provided +dispatch('change'); +dispatch('change', 'string'); +// @ts-expect-error: wrong type of detail +dispatch('change', 123); +// @ts-expect-error: wrong type of detail +dispatch('change', undefined); + +dispatch('valid', true); +// @ts-expect-error: wrong type of detail +dispatch('valid', 'string'); + +dispatch('optional'); +dispatch('optional', 123); +dispatch('optional', null); +// @ts-expect-error: wrong type of optional detail +dispatch('optional', 'string'); diff --git a/test/types/tsconfig.json b/test/types/tsconfig.json new file mode 100644 index 000000000000..027fca0dc1df --- /dev/null +++ b/test/types/tsconfig.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "moduleResolution": "node", + "module": "ESNext", + "lib": ["ESNext", "DOM"], + "target": "ESNext", + "isolatedModules": true, + "resolveJsonModule": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "baseUrl": "../../", + "allowJs": true, + "checkJs": true, + "paths": { + "$runtime/*": ["src/runtime/*"] + }, + // enable strictest options + "allowUnreachableCode": false, + "alwaysStrict": true, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "strict": true, + }, + "include": ["**/*.test-types.ts"] +} From 281ee3fd91a4ec0e8a6498c314c3574fe6f552b3 Mon Sep 17 00:00:00 2001 From: Ivan Hofer Date: Sat, 12 Feb 2022 19:34:58 +0100 Subject: [PATCH 05/13] upgrade to typescript `3.9.10` --- package-lock.json | 15 ++++++++------- package.json | 2 +- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index e9b672d689c3..037ad6cf18a7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,6 +5,7 @@ "requires": true, "packages": { "": { + "name": "svelte", "version": "3.46.4", "license": "MIT", "devDependencies": { @@ -43,7 +44,7 @@ "sourcemap-codec": "^1.4.8", "tiny-glob": "^0.2.6", "tslib": "^2.0.3", - "typescript": "^3.7.5" + "typescript": "^3.9.10" }, "engines": { "node": ">= 8" @@ -4870,9 +4871,9 @@ "dev": true }, "node_modules/typescript": { - "version": "3.7.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.7.5.tgz", - "integrity": "sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw==", + "version": "3.9.10", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.10.tgz", + "integrity": "sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -8980,9 +8981,9 @@ "dev": true }, "typescript": { - "version": "3.7.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.7.5.tgz", - "integrity": "sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw==", + "version": "3.9.10", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.10.tgz", + "integrity": "sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q==", "dev": true }, "unbox-primitive": { diff --git a/package.json b/package.json index 526cdae9e10d..bf07168b8608 100644 --- a/package.json +++ b/package.json @@ -148,7 +148,7 @@ "sourcemap-codec": "^1.4.8", "tiny-glob": "^0.2.6", "tslib": "^2.0.3", - "typescript": "^3.7.5" + "typescript": "^3.9.10" }, "nyc": { "include": [ From 49b869b1dd22feac8767273c00db68ff1b3d003f Mon Sep 17 00:00:00 2001 From: Ivan Hofer Date: Sat, 12 Feb 2022 20:10:17 +0100 Subject: [PATCH 06/13] add some types to `utils.ts` --- src/compiler/compile/render_dom/invalidate.ts | 1 + src/runtime/internal/utils.ts | 78 ++++++++++--------- src/runtime/store/index.ts | 2 +- 3 files changed, 44 insertions(+), 37 deletions(-) diff --git a/src/compiler/compile/render_dom/invalidate.ts b/src/compiler/compile/render_dom/invalidate.ts index 7eeb34a89400..31df2cbe3b5b 100644 --- a/src/compiler/compile/render_dom/invalidate.ts +++ b/src/compiler/compile/render_dom/invalidate.ts @@ -50,6 +50,7 @@ export function invalidate(renderer: Renderer, scope: Scope, node: Node, names: const extra_args = tail.map(variable => get_invalidated(variable)).filter(Boolean); if (is_store_value) { + // TODO: check why there are 4 parameters, but `set_store_value` only expects 3 return x`@set_store_value(${head.name.slice(1)}, ${node}, ${head.name}, ${extra_args})`; } diff --git a/src/runtime/internal/utils.ts b/src/runtime/internal/utils.ts index 8868e38ee29f..0111c2a94a18 100644 --- a/src/runtime/internal/utils.ts +++ b/src/runtime/internal/utils.ts @@ -1,13 +1,16 @@ -import { Readable } from 'svelte/store'; +import { Readable, Subscriber, Invalidator, Writable } from 'svelte/store'; +import { SvelteComponent } from '..'; -export function noop() {} +export function noop() { } -export const identity = x => x; +export function identity(x: T): T { + return x; +} -export function assign(tar: T, src: S): T & S { +export function assign(target: T, source: S): T & S { // @ts-ignore - for (const k in src) tar[k] = src[k]; - return tar as T & S; + for (const k in source) target[k] = source[k]; + return target as T & S; } export function is_promise(value: any): value is PromiseLike { @@ -20,15 +23,17 @@ export function add_location(element, file, line, column, char) { }; } -export function run(fn) { - return fn(); +export function blank_object(): {} { + return Object.create(null); } -export function blank_object() { - return Object.create(null); +type FunctionWithoutArguments = () => unknown + +export function run(fn: FunctionWithoutArguments) { + return fn(); } -export function run_all(fns: Function[]) { +export function run_all(fns: FunctionWithoutArguments[]) { fns.forEach(run); } @@ -36,13 +41,9 @@ export function is_function(thing: any): thing is Function { return typeof thing === 'function'; } -export function safe_not_equal(a, b) { - return a != a ? b == b : a !== b || ((a && typeof a === 'object') || typeof a === 'function'); -} - -let src_url_equal_anchor; +let src_url_equal_anchor: HTMLAnchorElement; -export function src_url_equal(element_src, url) { +export function src_url_equal(element_src: string, url: string) { if (!src_url_equal_anchor) { src_url_equal_anchor = document.createElement('a'); } @@ -50,52 +51,55 @@ export function src_url_equal(element_src, url) { return element_src === src_url_equal_anchor.href; } -export function not_equal(a, b) { +export function safe_not_equal(a: unknown, b: unknown) { + return a != a ? b == b : a !== b || ((a && typeof a === 'object') || typeof a === 'function'); +} + +export function not_equal(a: unknown, b: unknown) { return a != a ? b == b : a !== b; } -export function is_empty(obj) { +export function is_empty(obj: Record) { return Object.keys(obj).length === 0; } -export function validate_store(store, name) { +export function validate_store>(store: S, name: string) { if (store != null && typeof store.subscribe !== 'function') { throw new Error(`'${name}' is not a store with a 'subscribe' method`); } } -export function subscribe(store, ...callbacks) { +export function subscribe>(store: S, run: Subscriber, invalidate?: Invalidator) { if (store == null) { return noop; } - const unsub = store.subscribe(...callbacks); - return unsub.unsubscribe ? () => unsub.unsubscribe() : unsub; + return store.subscribe(run, invalidate); } -export function get_store_value(store: Readable): T { - let value; - subscribe(store, _ => value = _)(); +export function get_store_value>(store: S): T { + let value: T; + subscribe(store, v => value = v)(); return value; } -export function component_subscribe(component, store, callback) { +export function component_subscribe>(component: SvelteComponent, store: S, callback: Subscriber) { component.$$.on_destroy.push(subscribe(store, callback)); } -export function create_slot(definition, ctx, $$scope, fn) { +export function create_slot(definition, ctx, $$scope, fn: Function) { if (definition) { const slot_ctx = get_slot_context(definition, ctx, $$scope, fn); return definition[0](slot_ctx); } } -function get_slot_context(definition, ctx, $$scope, fn) { +function get_slot_context(definition, ctx, $$scope, fn: Function) { return definition[1] && fn ? assign($$scope.ctx.slice(), definition[1](fn(ctx))) : $$scope.ctx; } -export function get_slot_changes(definition, $$scope, dirty, fn) { +export function get_slot_changes(definition, $$scope, dirty, fn: Function) { if (definition[2] && fn) { const lets = definition[2](fn(dirty)); @@ -164,25 +168,27 @@ export function compute_slots(slots) { return result; } -export function once(fn) { +export function once(fn: Function) { let ran = false; - return function(this: any, ...args) { + return function (this: any, ...args: unknown[]) { if (ran) return; ran = true; fn.call(this, ...args); }; } -export function null_to_empty(value) { - return value == null ? '' : value; +export function null_to_empty(value: T): T extends null | undefined ? '' : T { + return (value == null ? '' : value) as T extends null | undefined ? '' : T; } -export function set_store_value(store, ret, value) { +export function set_store_value>(store: S, ret: Node, value: T) { store.set(value); return ret; } -export const has_prop = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop); +export function has_prop(obj: X, prop: Y): obj is X & Record { + return Object.prototype.hasOwnProperty.call(obj, prop); +} export function action_destroyer(action_result) { return action_result && is_function(action_result.destroy) ? action_result.destroy : noop; diff --git a/src/runtime/store/index.ts b/src/runtime/store/index.ts index e947fa074028..a3132342fa94 100644 --- a/src/runtime/store/index.ts +++ b/src/runtime/store/index.ts @@ -10,7 +10,7 @@ export type Unsubscriber = () => void; export type Updater = (value: T) => T; /** Cleanup logic callback. */ -type Invalidator = (value?: T) => void; +export type Invalidator = (value?: T) => void; /** Start and stop notification callbacks. */ export type StartStopNotifier = (set: Subscriber) => Unsubscriber | void; From 4c48f95bcebe10388038a0cabfcaa72565772cce Mon Sep 17 00:00:00 2001 From: Ivan Hofer Date: Sun, 13 Feb 2022 10:01:09 +0100 Subject: [PATCH 07/13] add more types --- src/runtime/{ambient.ts => ambient.d.ts} | 0 src/runtime/index.ts | 2 +- src/runtime/internal/dom.ts | 229 +++++++++++++---------- src/runtime/internal/utils.ts | 20 +- test/types/tsconfig.json | 12 +- tsconfig.json | 4 +- 6 files changed, 148 insertions(+), 119 deletions(-) rename src/runtime/{ambient.ts => ambient.d.ts} (100%) diff --git a/src/runtime/ambient.ts b/src/runtime/ambient.d.ts similarity index 100% rename from src/runtime/ambient.ts rename to src/runtime/ambient.d.ts diff --git a/src/runtime/index.ts b/src/runtime/index.ts index 8e12f9f0eebb..7fcd5a1603c2 100644 --- a/src/runtime/index.ts +++ b/src/runtime/index.ts @@ -1,4 +1,4 @@ -import './ambient'; +import './ambient.d.ts'; export { onMount, diff --git a/src/runtime/internal/dom.ts b/src/runtime/internal/dom.ts index 287c16b5fc5d..86f7c1e4fb0c 100644 --- a/src/runtime/internal/dom.ts +++ b/src/runtime/internal/dom.ts @@ -1,5 +1,31 @@ import { has_prop } from './utils'; +// marks a part in the code where types +// 1. should be improved or +// 2. where casting is needed in order to satisfy TypeScript +// a deeper look at these parts is needed to check if they can be replaced with a normal cast or if they currently contain a potential bug +type TODO = T + +type NodeEx = Node & { + claim_order?: number, + hydrate_init?: true, + actual_end_child?: NodeEx, + childNodes: NodeListOf, +} + +type HTMLInputElementEx = HTMLInputElement & { + __value: string +} + +type HTMLSelectElementEx = HTMLSelectElement & { + __value: string + options: HTMLOptionElementEx[] +} + +type HTMLOptionElementEx = HTMLOptionElement & { + __value: string +} + // Track which nodes are claimed during hydration. Unclaimed nodes can then be removed from the DOM // at the end of hydration without touching the remaining nodes. let is_hydrating = false; @@ -11,13 +37,6 @@ export function end_hydrating() { is_hydrating = false; } -type NodeEx = Node & { - claim_order?: number, - hydrate_init? : true, - actual_end_child?: NodeEx, - childNodes: NodeListOf, -}; - function upper_bound(low: number, high: number, key: (index: number) => number, value: number) { // Return first index of value larger than input value in the range [low, high) while (low < high) { @@ -35,7 +54,7 @@ function init_hydrate(target: NodeEx) { if (target.hydrate_init) return; target.hydrate_init = true; - type NodeEx2 = NodeEx & {claim_order: number}; + type NodeEx2 = NodeEx & { claim_order: number }; // We know that all children have claim_order values since the unclaimed have been detached if target is not let children: ArrayLike = target.childNodes as NodeListOf; @@ -151,7 +170,7 @@ export function get_root_for_style(node: Node): ShadowRoot | Document { if (root && (root as ShadowRoot).host) { return root as ShadowRoot; } - return node.ownerDocument; + return node.ownerDocument as TODO; } export function append_empty_stylesheet(node: Node) { @@ -169,12 +188,12 @@ export function append_hydration(target: NodeEx, node: NodeEx) { init_hydrate(target); if ((target.actual_end_child === undefined) || ((target.actual_end_child !== null) && (target.actual_end_child.parentElement !== target))) { - target.actual_end_child = target.firstChild; + target.actual_end_child = target.firstChild as TODO; } // Skip nodes of undefined ordering - while ((target.actual_end_child !== null) && (target.actual_end_child.claim_order === undefined)) { - target.actual_end_child = target.actual_end_child.nextSibling; + while ((target.actual_end_child !== null) && ((target.actual_end_child as NodeEx).claim_order === undefined)) { + target.actual_end_child = (target.actual_end_child as NodeEx).nextSibling as TODO; } if (node !== target.actual_end_child) { @@ -183,7 +202,7 @@ export function append_hydration(target: NodeEx, node: NodeEx) { target.insertBefore(node, target.actual_end_child); } } else { - target.actual_end_child = node.nextSibling; + target.actual_end_child = node.nextSibling as ChildNode | undefined; } } else if (node.parentNode !== target || node.nextSibling !== null) { target.appendChild(node); @@ -203,10 +222,27 @@ export function insert_hydration(target: NodeEx, node: NodeEx, anchor?: NodeEx) } export function detach(node: Node) { - node.parentNode.removeChild(node); -} - -export function destroy_each(iterations, detaching) { + (node.parentNode as TODO).removeChild(node); +} + +// TODO: import this interface from /src/runtime/internal/Component.ts +interface Fragment { + key: string | null; + first: null; + /* create */ c: () => void; + /* claim */ l: (nodes: any) => void; + /* hydrate */ h: () => void; + /* mount */ m: (target: HTMLElement, anchor: any) => void; + /* update */ p: (ctx: any, dirty: any) => void; + /* measure */ r: () => void; + /* fix */ f: () => void; + /* animate */ a: () => void; + /* intro */ i: (local: any) => void; + /* outro */ o: (local: any) => void; + /* destroy */ d: (detaching: 0 | 1) => void; +} + +export function destroy_each(iterations: Fragment[], detaching: 0 | 1) { for (let i = 0; i < iterations.length; i += 1) { if (iterations[i]) iterations[i].d(detaching); } @@ -256,32 +292,28 @@ export function listen(node: EventTarget, event: string, handler: EventListenerO return () => node.removeEventListener(event, handler, options); } -export function prevent_default(fn) { - return function(event) { +export function prevent_default(fn: (event: E) => void) { + return function (this: unknown, event: E) { event.preventDefault(); - // @ts-ignore return fn.call(this, event); }; } -export function stop_propagation(fn) { - return function(event) { +export function stop_propagation(fn: (event: E) => void) { + return function (this: unknown, event: E) { event.stopPropagation(); - // @ts-ignore return fn.call(this, event); }; } -export function self(fn) { - return function(event) { - // @ts-ignore +export function self(fn: (event: E) => void) { + return function (this: unknown, event: E) { if (event.target === this) fn.call(this, event); }; } -export function trusted(fn) { - return function(event) { - // @ts-ignore +export function trusted(fn: (event: E) => void) { + return function (this: unknown, event: E) { if (event.isTrusted) fn.call(this, event); }; } @@ -291,20 +323,20 @@ export function attr(node: Element, attribute: string, value?: string) { else if (node.getAttribute(attribute) !== value) node.setAttribute(attribute, value); } -export function set_attributes(node: Element & ElementCSSInlineStyle, attributes: { [x: string]: string }) { +export function set_attributes(node: N, attributes: { [K in keyof N]: N[K] }) { // @ts-ignore const descriptors = Object.getOwnPropertyDescriptors(node.__proto__); for (const key in attributes) { if (attributes[key] == null) { node.removeAttribute(key); } else if (key === 'style') { - node.style.cssText = attributes[key]; + node.style.cssText = attributes[key] as unknown as TODO; } else if (key === '__value') { (node as any).value = node[key] = attributes[key]; } else if (descriptors[key] && descriptors[key].set) { node[key] = attributes[key]; } else { - attr(node, key, attributes[key]); + attr(node, key, attributes[key] as unknown as TODO); } } } @@ -315,19 +347,20 @@ export function set_svg_attributes(node: Element & ElementCSSInlineStyle, attrib } } -export function set_custom_element_data(node, prop, value) { +export function set_custom_element_data(node: Element, prop: string, value: string) { if (prop in node) { - node[prop] = typeof node[prop] === 'boolean' && value === '' ? true : value; + //@ts-ignore + node[prop] = typeof node[(prop as keyof typeof node)] === 'boolean' && value === '' ? true : value; } else { attr(node, prop, value); } } -export function xlink_attr(node, attribute, value) { +export function xlink_attr(node: Element, attribute: string, value: string) { node.setAttributeNS('http://www.w3.org/1999/xlink', attribute, value); } -export function get_binding_group_value(group, __value, checked) { +export function get_binding_group_value(group: HTMLInputElementEx[], __value: string, checked: boolean) { const value = new Set(); for (let i = 0; i < group.length; i += 1) { if (group[i].checked) value.add(group[i].__value); @@ -338,11 +371,11 @@ export function get_binding_group_value(group, __value, checked) { return Array.from(value); } -export function to_number(value) { +export function to_number(value: TODO) { return value === '' ? null : +value; } -export function time_ranges_to_array(ranges) { +export function time_ranges_to_array(ranges: TimeRanges) { const array = []; for (let i = 0; i < ranges.length; i += 1) { array.push({ start: ranges.start(i), end: ranges.end(i) }); @@ -353,17 +386,19 @@ export function time_ranges_to_array(ranges) { type ChildNodeEx = ChildNode & NodeEx; type ChildNodeArray = ChildNodeEx[] & { - claim_info?: { - /** - * The index of the last claimed element - */ - last_index: number; - /** - * The total number of elements claimed - */ - total_claimed: number; - } -}; + claim_info?: ClaimInfo +} + +type ClaimInfo = { + /** + * The index of the last claimed element + */ + last_index: number; + /** + * The total number of elements claimed + */ + total_claimed: number; +} export function children(element: Element) { return Array.from(element.childNodes); @@ -371,17 +406,17 @@ export function children(element: Element) { function init_claim_info(nodes: ChildNodeArray) { if (nodes.claim_info === undefined) { - nodes.claim_info = {last_index: 0, total_claimed: 0}; + nodes.claim_info = { last_index: 0, total_claimed: 0 }; } } -function claim_node(nodes: ChildNodeArray, predicate: (node: ChildNodeEx) => node is R, processNode: (node: ChildNodeEx) => ChildNodeEx | undefined, createNode: () => R, dontUpdateLastIndex: boolean = false) { +function claim_node(nodes: ChildNodeArray, predicate: (node: ChildNodeEx) => node is R, processNode: (node: R) => R | undefined, createNode: () => R, dontUpdateLastIndex: boolean = false) { // Try to find nodes in an order such that we lengthen the longest increasing subsequence init_claim_info(nodes); const resultNode = (() => { // We first try to find an element after the previous one - for (let i = nodes.claim_info.last_index; i < nodes.length; i++) { + for (let i = (nodes.claim_info as TODO).last_index; i < nodes.length; i++) { const node = nodes[i]; if (predicate(node)) { @@ -393,7 +428,7 @@ function claim_node(nodes: ChildNodeArray, predicate: (no nodes[i] = replacement; } if (!dontUpdateLastIndex) { - nodes.claim_info.last_index = i; + (nodes.claim_info as TODO).last_index = i; } return node; } @@ -402,7 +437,7 @@ function claim_node(nodes: ChildNodeArray, predicate: (no // Otherwise, we try to find one before // We iterate in reverse so that we don't go too far back - for (let i = nodes.claim_info.last_index - 1; i >= 0; i--) { + for (let i = (nodes.claim_info as TODO).last_index - 1; i >= 0; i--) { const node = nodes[i]; if (predicate(node)) { @@ -414,10 +449,10 @@ function claim_node(nodes: ChildNodeArray, predicate: (no nodes[i] = replacement; } if (!dontUpdateLastIndex) { - nodes.claim_info.last_index = i; + (nodes.claim_info as TODO).last_index = i; } else if (replacement === undefined) { // Since we spliced before the last_index, we decrease it - nodes.claim_info.last_index--; + (nodes.claim_info as TODO).last_index--; } return node; } @@ -427,16 +462,16 @@ function claim_node(nodes: ChildNodeArray, predicate: (no return createNode(); })(); - resultNode.claim_order = nodes.claim_info.total_claimed; - nodes.claim_info.total_claimed += 1; + resultNode.claim_order = (nodes.claim_info as TODO).total_claimed; + (nodes.claim_info as TODO).total_claimed += 1; return resultNode; } function claim_element_base(nodes: ChildNodeArray, name: string, attributes: { [key: string]: boolean }, create_element: (name: string) => Element | SVGElement) { return claim_node( nodes, - (node: ChildNode): node is Element | SVGElement => node.nodeName === name, - (node: Element) => { + (node): node is Element | SVGElement => node.nodeName === name, + (node) => { const remove = []; for (let j = 0; j < node.attributes.length; j++) { const attribute = node.attributes[j]; @@ -459,11 +494,11 @@ export function claim_svg_element(nodes: ChildNodeArray, name: string, attribute return claim_element_base(nodes, name, attributes, svg_element); } -export function claim_text(nodes: ChildNodeArray, data) { +export function claim_text(nodes: ChildNodeArray, data: string) { return claim_node( nodes, - (node: ChildNode): node is Text => node.nodeType === 3, - (node: Text) => { + (node): node is Text => node.nodeType === 3, + (node) => { const dataStr = '' + data; if (node.data.startsWith(dataStr)) { if (node.data.length !== dataStr.length) { @@ -478,21 +513,21 @@ export function claim_text(nodes: ChildNodeArray, data) { ); } -export function claim_space(nodes) { +export function claim_space(nodes: ChildNodeArray) { return claim_text(nodes, ' '); } -function find_comment(nodes, text, start) { +function find_comment(nodes: ChildNodeArray, text: string, start: number) { for (let i = start; i < nodes.length; i += 1) { const node = nodes[i]; - if (node.nodeType === 8 /* comment node */ && node.textContent.trim() === text) { + if (node.nodeType === 8 /* comment node */ && (node.textContent as TODO).trim() === text) { return i; } } return nodes.length; } -export function claim_html_tag(nodes) { +export function claim_html_tag(nodes: ChildNodeArray) { // find html opening tag const start_index = find_comment(nodes, 'HTML_TAG_START', 0); const end_index = find_comment(nodes, 'HTML_TAG_END', start_index); @@ -506,22 +541,22 @@ export function claim_html_tag(nodes) { detach(html_tag_nodes[html_tag_nodes.length - 1]); const claimed_nodes = html_tag_nodes.slice(1, html_tag_nodes.length - 1); for (const n of claimed_nodes) { - n.claim_order = nodes.claim_info.total_claimed; - nodes.claim_info.total_claimed += 1; + n.claim_order = (nodes.claim_info as TODO).total_claimed; + (nodes.claim_info as TODO).total_claimed += 1; } return new HtmlTagHydration(claimed_nodes); } -export function set_data(text, data) { +export function set_data(text: Text, data: string) { data = '' + data; if (text.wholeText !== data) text.data = data; } -export function set_input_value(input, value) { +export function set_input_value(input: HTMLInputElement, value: string | null) { input.value = value == null ? '' : value; } -export function set_input_type(input, type) { +export function set_input_type(input: HTMLInputElement, type: string) { try { input.type = type; } catch (e) { @@ -529,7 +564,7 @@ export function set_input_type(input, type) { } } -export function set_style(node, key, value, important) { +export function set_style(node: HTMLElement, key: string, value: string, important: boolean) { if (value === null) { node.style.removeProperty(key); } else { @@ -537,7 +572,7 @@ export function set_style(node, key, value, important) { } } -export function select_option(select, value) { +export function select_option(select: HTMLSelectElementEx, value: string) { for (let i = 0; i < select.options.length; i += 1) { const option = select.options[i]; @@ -550,20 +585,20 @@ export function select_option(select, value) { select.selectedIndex = -1; // no option should be selected } -export function select_options(select, value) { +export function select_options(select: HTMLSelectElementEx, value: string) { for (let i = 0; i < select.options.length; i += 1) { const option = select.options[i]; - option.selected = ~value.indexOf(option.__value); + option.selected = ~value.indexOf(option.__value) as unknown as TODO; } } -export function select_value(select) { - const selected_option = select.querySelector(':checked') || select.options[0]; +export function select_value(select: HTMLSelectElementEx) { + const selected_option = (select.querySelector(':checked') || select.options[0]) as HTMLOptionElementEx; return selected_option && selected_option.__value; } -export function select_multiple_value(select) { - return [].map.call(select.querySelectorAll(':checked'), option => option.__value); +export function select_multiple_value(select: HTMLSelectElement) { + return [].map.call(select.querySelectorAll(':checked'), (option: HTMLOptionElementEx) => option.__value); } // unfortunately this can't be a constant as that wouldn't be tree-shakeable @@ -607,13 +642,13 @@ export function add_resize_listener(node: HTMLElement, fn: () => void) { if (crossorigin) { iframe.src = "data:text/html,"; - unsubscribe = listen(window, 'message', (event: MessageEvent) => { + unsubscribe = listen(window, 'message', ((event: MessageEvent) => { if (event.source === iframe.contentWindow) fn(); - }); + }) as TODO); } else { iframe.src = 'about:blank'; iframe.onload = () => { - unsubscribe = listen(iframe.contentWindow, 'resize', fn); + unsubscribe = listen(iframe.contentWindow as TODO, 'resize', fn); }; } @@ -630,13 +665,14 @@ export function add_resize_listener(node: HTMLElement, fn: () => void) { }; } -export function toggle_class(element, name, toggle) { +export function toggle_class(element: HTMLElement, name: string, toggle: true) { + // TODO: why not use `element.classList.toggle` ? element.classList[toggle ? 'add' : 'remove'](name); } -export function custom_event(type: string, detail?: T, bubbles: boolean = false) { +export function custom_event(type: string, detail?: T, bubbles: boolean = false) { const e: CustomEvent = document.createEvent('CustomEvent'); - e.initCustomEvent(type, bubbles, false, detail); + e.initCustomEvent(type, bubbles, false, detail as TODO); return e; } @@ -650,11 +686,14 @@ export class HtmlTag { // html tag nodes n: ChildNode[]; // target + //@ts-ignore t: HTMLElement; // anchor + //@ts-ignore a: HTMLElement; constructor() { + //@ts-ignore this.e = this.n = null; } @@ -662,13 +701,14 @@ export class HtmlTag { this.h(html); } - m(html: string, target: HTMLElement, anchor: HTMLElement = null) { + m(html: string, target: HTMLElement, anchor: HTMLElement | null = null) { if (!this.e) { this.e = element(target.nodeName as keyof HTMLElementTagNameMap); this.t = target; this.c(html); } + //@ts-ignore this.i(anchor); } @@ -677,7 +717,7 @@ export class HtmlTag { this.n = Array.from(this.e.childNodes); } - i(anchor) { + i(anchor: Node) { for (let i = 0; i < this.n.length; i += 1) { insert(this.t, this.n[i], anchor); } @@ -700,6 +740,7 @@ export class HtmlTagHydration extends HtmlTag { constructor(claimed_nodes?: ChildNode[]) { super(); + //@ts-ignore this.e = this.n = null; this.l = claimed_nodes; } @@ -710,7 +751,7 @@ export class HtmlTagHydration extends HtmlTag { super.c(html); } } - i(anchor) { + i(anchor: NodeEx) { for (let i = 0; i < this.n.length; i += 1) { insert_hydration(this.t, this.n[i], anchor); } @@ -718,17 +759,17 @@ export class HtmlTagHydration extends HtmlTag { } export function attribute_to_object(attributes: NamedNodeMap) { - const result = {}; - for (const attribute of attributes) { + const result: Record = {}; + for (const attribute of attributes as unknown as Iterable) { result[attribute.name] = attribute.value; } return result; } export function get_custom_elements_slots(element: HTMLElement) { - const result = {}; - element.childNodes.forEach((node: Element) => { - result[node.slot || 'default'] = true; + const result: Record = {}; + element.childNodes.forEach((node) => { + result[(node as TODO).slot || 'default'] = true; }); return result; } diff --git a/src/runtime/internal/utils.ts b/src/runtime/internal/utils.ts index 0111c2a94a18..74ca27fdfefa 100644 --- a/src/runtime/internal/utils.ts +++ b/src/runtime/internal/utils.ts @@ -27,13 +27,11 @@ export function blank_object(): {} { return Object.create(null); } -type FunctionWithoutArguments = () => unknown - -export function run(fn: FunctionWithoutArguments) { +export function run(fn: Function) { return fn(); } -export function run_all(fns: FunctionWithoutArguments[]) { +export function run_all(fns: Function[]) { fns.forEach(run); } @@ -59,7 +57,7 @@ export function not_equal(a: unknown, b: unknown) { return a != a ? b == b : a !== b; } -export function is_empty(obj: Record) { +export function is_empty(obj: Record) { return Object.keys(obj).length === 0; } @@ -147,21 +145,21 @@ export function get_all_dirty_from_scope($$scope) { return -1; } -export function exclude_internal_props(props) { - const result = {}; +export function exclude_internal_props

>(props: P) { + const result: Partial

= {}; for (const k in props) if (k[0] !== '$') result[k] = props[k]; return result; } -export function compute_rest_props(props, keys) { - const rest = {}; +export function compute_rest_props

>(props: P, keys: string[] | Set) { + const rest: Partial

= {}; keys = new Set(keys); for (const k in props) if (!keys.has(k) && k[0] !== '$') rest[k] = props[k]; return rest; } -export function compute_slots(slots) { - const result = {}; +export function compute_slots>(slots: S) { + const result = {} as Record; for (const key in slots) { result[key] = true; } diff --git a/test/types/tsconfig.json b/test/types/tsconfig.json index 027fca0dc1df..5870e293c0c7 100644 --- a/test/types/tsconfig.json +++ b/test/types/tsconfig.json @@ -1,17 +1,7 @@ { + "extends": "../../tsconfig.json", "compilerOptions": { - "moduleResolution": "node", - "module": "ESNext", - "lib": ["ESNext", "DOM"], - "target": "ESNext", - "isolatedModules": true, - "resolveJsonModule": true, - "esModuleInterop": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, "baseUrl": "../../", - "allowJs": true, - "checkJs": true, "paths": { "$runtime/*": ["src/runtime/*"] }, diff --git a/tsconfig.json b/tsconfig.json index f2e055a2a95a..56be920ecb06 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,7 +6,7 @@ // target node v8+ (https://node.green/) // the only missing feature is Array.prototype.values - "lib": ["es2017"], + "lib": ["es2017", "DOM"], "target": "es2017", "declaration": true, @@ -25,7 +25,7 @@ "stripInternal": true, // TODO: error all the things - //"strict": true, + // "strict": true, "noImplicitThis": true, "noUnusedLocals": true, "noUnusedParameters": true, From c5594717ab1d31cb1bee44939061cc9bee2040a6 Mon Sep 17 00:00:00 2001 From: Ivan Hofer Date: Thu, 23 Feb 2023 08:11:01 +0100 Subject: [PATCH 08/13] fix types --- src/runtime/internal/dom.ts | 20 +++---- src/runtime/internal/lifecycle.ts | 4 ++ src/runtime/internal/utils.ts | 2 +- .../runtime/internal/lifecyle.test-types.ts | 56 ++++++++++--------- 4 files changed, 42 insertions(+), 40 deletions(-) diff --git a/src/runtime/internal/dom.ts b/src/runtime/internal/dom.ts index f45e60cfefe4..e2ef368cf193 100644 --- a/src/runtime/internal/dom.ts +++ b/src/runtime/internal/dom.ts @@ -350,15 +350,9 @@ export function set_svg_attributes(node: Element & ElementCSSInlineStyle, attrib } } -export function set_custom_element_data_map(node, data_map: Record) { +export function set_custom_element_data_map(node: Element, data_map: Record) { Object.keys(data_map).forEach((key) => { - set_custom_element_data(node, key, data_map[key]); - }); -} - -export function set_custom_element_data_map(node, data_map: Record) { - Object.keys(data_map).forEach((key) => { - set_custom_element_data(node, key, data_map[key]); + set_custom_element_data(node, key, data_map[key] as TODO); }); } @@ -548,7 +542,8 @@ export function claim_html_tag(nodes: ChildNodeArray, is_svg: boolean) { const start_index = find_comment(nodes, 'HTML_TAG_START', 0); const end_index = find_comment(nodes, 'HTML_TAG_END', start_index); if (start_index === end_index) { - return new HtmlTagHydration(undefined, is_svg); + // @ts-expect-error `HtmlTagHydration` does not expect a second parameter + return new HtmlTagHydration(undefined, is_svg); } init_claim_info(nodes); @@ -560,6 +555,7 @@ export function claim_html_tag(nodes: ChildNodeArray, is_svg: boolean) { n.claim_order = (nodes.claim_info as TODO).total_claimed; (nodes.claim_info as TODO).total_claimed += 1; } + // @ts-expect-error `HtmlTagHydration` does not expect a second parameter return new HtmlTagHydration(claimed_nodes, is_svg); } @@ -779,8 +775,8 @@ export class HtmlTagHydration extends HtmlTag { // hydration claimed nodes l: ChildNode[] | void; - constructor(claimed_nodes?: ChildNode[], is_svg: boolean = false) { - super(is_svg); + constructor(claimed_nodes?: ChildNode[]) { + super(); this.e = this.n = null; this.l = claimed_nodes; } @@ -814,6 +810,6 @@ export function get_custom_elements_slots(element: HTMLElement) { return result; } -export function construct_svelte_component(component, props) { +export function construct_svelte_component(component: TODO, props: Record) { return new component(props); } diff --git a/src/runtime/internal/lifecycle.ts b/src/runtime/internal/lifecycle.ts index 503055201a95..7a6fac2c694f 100644 --- a/src/runtime/internal/lifecycle.ts +++ b/src/runtime/internal/lifecycle.ts @@ -56,6 +56,10 @@ export function onDestroy(fn: () => any) { get_current_component().$$.on_destroy.push(fn); } +export interface DispatchOptions { + cancelable?: boolean; +} + /** * Creates an event dispatcher that can be used to dispatch [component events](/docs#template-syntax-component-directives-on-eventname). * Event dispatchers are functions that can take two arguments: `name` and `detail`. diff --git a/src/runtime/internal/utils.ts b/src/runtime/internal/utils.ts index b4c5898f3df0..79c3ccba4273 100644 --- a/src/runtime/internal/utils.ts +++ b/src/runtime/internal/utils.ts @@ -1,5 +1,5 @@ import { Readable, Subscriber, Invalidator, Writable } from 'svelte/store'; -import { SvelteComponent } from '..'; +import { SvelteComponent } from '../index.js'; export function noop() { } diff --git a/test/types/runtime/internal/lifecyle.test-types.ts b/test/types/runtime/internal/lifecyle.test-types.ts index 6190f675a597..6a3ebdd6666c 100644 --- a/test/types/runtime/internal/lifecyle.test-types.ts +++ b/test/types/runtime/internal/lifecyle.test-types.ts @@ -1,33 +1,35 @@ -import { createEventDispatcher } from '$runtime/internal/lifecycle'; +// TODO: enable those tests when #7224 got merged -const dispatch = createEventDispatcher<{ - loaded: never - change: string - valid: boolean - optional: number | null -}>(); +// import { createEventDispatcher } from '$runtime/internal/lifecycle'; -// @ts-expect-error: dispatch invalid event -dispatch('some-event'); +// const dispatch = createEventDispatcher<{ +// loaded: never +// change: string +// valid: boolean +// optional: number | null +// }>(); -dispatch('loaded'); -// @ts-expect-error: no detail accepted -dispatch('loaded', 123); +// // @ts-expect-error: dispatch invalid event +// dispatch('some-event'); -// @ts-expect-error: detail not provided -dispatch('change'); -dispatch('change', 'string'); -// @ts-expect-error: wrong type of detail -dispatch('change', 123); -// @ts-expect-error: wrong type of detail -dispatch('change', undefined); +// dispatch('loaded'); +// // @ts-expect-error: no detail accepted +// dispatch('loaded', 123); -dispatch('valid', true); -// @ts-expect-error: wrong type of detail -dispatch('valid', 'string'); +// // @ts-expect-error: detail not provided +// dispatch('change'); +// dispatch('change', 'string'); +// // @ts-expect-error: wrong type of detail +// dispatch('change', 123); +// // @ts-expect-error: wrong type of detail +// dispatch('change', undefined); -dispatch('optional'); -dispatch('optional', 123); -dispatch('optional', null); -// @ts-expect-error: wrong type of optional detail -dispatch('optional', 'string'); +// dispatch('valid', true); +// // @ts-expect-error: wrong type of detail +// dispatch('valid', 'string'); + +// dispatch('optional'); +// dispatch('optional', 123); +// dispatch('optional', null); +// // @ts-expect-error: wrong type of optional detail +// dispatch('optional', 'string'); From c5de00fe8237d6f498326b32531e7c4cec62db5c Mon Sep 17 00:00:00 2001 From: Ivan Hofer Date: Thu, 23 Feb 2023 08:25:39 +0100 Subject: [PATCH 09/13] fixes --- src/runtime/internal/lifecycle.ts | 2 +- src/runtime/internal/utils.ts | 24 +++++++++++++----------- src/runtime/store/index.ts | 2 +- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/runtime/internal/lifecycle.ts b/src/runtime/internal/lifecycle.ts index 7a6fac2c694f..e75bbdc501f4 100644 --- a/src/runtime/internal/lifecycle.ts +++ b/src/runtime/internal/lifecycle.ts @@ -140,7 +140,7 @@ export function getAllContexts = Map>(): T { * https://svelte.dev/docs#run-time-svelte-hascontext */ export function hasContext(key): boolean { - return get_current_component().$$.context.has(key); + return get_current_component().$$.context.has(key); } // TODO figure out if we still want to support diff --git a/src/runtime/internal/utils.ts b/src/runtime/internal/utils.ts index 79c3ccba4273..bc2f2f6e7ac5 100644 --- a/src/runtime/internal/utils.ts +++ b/src/runtime/internal/utils.ts @@ -1,16 +1,16 @@ -import { Readable, Subscriber, Invalidator, Writable } from 'svelte/store'; +import { Readable, Subscriber, Writable } from 'svelte/store'; import { SvelteComponent } from '../index.js'; -export function noop() { } +type TODO = T -export function identity(x: T): T { - return x; -} +export function noop() {} + +export const identity = (x: T): T => x; -export function assign(target: T, source: S): T & S { +export function assign(tar: T, src: S): T & S { // @ts-ignore - for (const k in source) target[k] = source[k]; - return target as T & S; + for (const k in src) tar[k] = src[k]; + return tar as T & S; } // Adapted from https://github.com/then/is-promise/blob/master/index.js @@ -69,11 +69,13 @@ export function validate_store>(store: S, name: stri } } -export function subscribe>(store: S, run: Subscriber, invalidate?: Invalidator) { +export function subscribe>(store: S, ...callbacks: Array>) { if (store == null) { return noop; } - return store.subscribe(run, invalidate); + // TODO: `store` does not accept an array of callbacks + const unsub: TODO = store.subscribe(...(callbacks as [TODO])); + return unsub.unsubscribe ? () => unsub.unsubscribe() : unsub; } export function get_store_value>(store: S): T { @@ -170,7 +172,7 @@ export function compute_slots>(slots: S) { export function once(fn: Function) { let ran = false; - return function (this: any, ...args: unknown[]) { + return function(this: any, ...args: unknown[]) { if (ran) return; ran = true; fn.call(this, ...args); diff --git a/src/runtime/store/index.ts b/src/runtime/store/index.ts index 30f5faf27d43..16832f10d90d 100644 --- a/src/runtime/store/index.ts +++ b/src/runtime/store/index.ts @@ -10,7 +10,7 @@ export type Unsubscriber = () => void; export type Updater = (value: T) => T; /** Cleanup logic callback. */ -export type Invalidator = (value?: T) => void; +type Invalidator = (value?: T) => void; /** Start and stop notification callbacks. */ export type StartStopNotifier = (set: Subscriber) => Unsubscriber | void; From d8f28f86126e4e49606d905f40887f869318a250 Mon Sep 17 00:00:00 2001 From: Ivan Hofer Date: Thu, 23 Feb 2023 08:27:10 +0100 Subject: [PATCH 10/13] remove TODO type --- src/runtime/internal/utils.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/runtime/internal/utils.ts b/src/runtime/internal/utils.ts index bc2f2f6e7ac5..01cf1e416923 100644 --- a/src/runtime/internal/utils.ts +++ b/src/runtime/internal/utils.ts @@ -1,8 +1,6 @@ import { Readable, Subscriber, Writable } from 'svelte/store'; import { SvelteComponent } from '../index.js'; -type TODO = T - export function noop() {} export const identity = (x: T): T => x; @@ -74,7 +72,7 @@ export function subscribe>(store: S, ...callbacks: Arra return noop; } // TODO: `store` does not accept an array of callbacks - const unsub: TODO = store.subscribe(...(callbacks as [TODO])); + const unsub: any = store.subscribe(...(callbacks as [any])); return unsub.unsubscribe ? () => unsub.unsubscribe() : unsub; } From 5f232ae123374a040e62aa3abdb98f1503f0d037 Mon Sep 17 00:00:00 2001 From: Ivan Hofer Date: Fri, 24 Feb 2023 07:20:46 +0100 Subject: [PATCH 11/13] add more types --- src/runtime/internal/dom.ts | 61 ++++++++++++++++++----------------- src/runtime/internal/utils.ts | 22 +++++++------ tsconfig.json | 2 +- 3 files changed, 45 insertions(+), 40 deletions(-) diff --git a/src/runtime/internal/dom.ts b/src/runtime/internal/dom.ts index e2ef368cf193..bd00f88da344 100644 --- a/src/runtime/internal/dom.ts +++ b/src/runtime/internal/dom.ts @@ -1,4 +1,5 @@ import { has_prop } from './utils'; +import { Fragment } from './types.js'; // marks a part in the code where types // 1. should be improved or @@ -228,23 +229,6 @@ export function detach(node: Node) { } } -// TODO: import this interface from /src/runtime/internal/Component.ts -interface Fragment { - key: string | null; - first: null; - /* create */ c: () => void; - /* claim */ l: (nodes: any) => void; - /* hydrate */ h: () => void; - /* mount */ m: (target: HTMLElement, anchor: any) => void; - /* update */ p: (ctx: any, dirty: any) => void; - /* measure */ r: () => void; - /* fix */ f: () => void; - /* animate */ a: () => void; - /* intro */ i: (local: any) => void; - /* outro */ o: (local: any) => void; - /* destroy */ d: (detaching: 0 | 1) => void; -} - export function destroy_each(iterations: Fragment[], detaching: 0 | 1) { for (let i = 0; i < iterations.length; i += 1) { if (iterations[i]) iterations[i].d(detaching); @@ -274,7 +258,7 @@ export function object_without_properties(obj: T, exclude: return target; } -export function svg_element(name: K): SVGElement { +export function svg_element(name: K): SVGElementTagNameMap[K] { return document.createElementNS('http://www.w3.org/2000/svg', name); } @@ -296,27 +280,27 @@ export function listen(node: EventTarget, event: string, handler: EventListenerO } export function prevent_default(fn: (event: E) => void) { - return function (this: unknown, event: E) { + return function(this: unknown, event: E) { event.preventDefault(); return fn.call(this, event); }; } export function stop_propagation(fn: (event: E) => void) { - return function (this: unknown, event: E) { + return function(this: unknown, event: E) { event.stopPropagation(); return fn.call(this, event); }; } export function self(fn: (event: E) => void) { - return function (this: unknown, event: E) { + return function(this: unknown, event: E) { if (event.target === this) fn.call(this, event); }; } export function trusted(fn: (event: E) => void) { - return function (this: unknown, event: E) { + return function(this: unknown, event: E) { if (event.isTrusted) fn.call(this, event); }; } @@ -358,7 +342,6 @@ export function set_custom_element_data_map(node: Element, data_map: Record(nodes: ChildNodeArray, predicate: (no return resultNode; } -function claim_element_base(nodes: ChildNodeArray, name: string, attributes: { [key: string]: boolean }, create_element: (name: string) => Element | SVGElement) { - return claim_node( +function claim_element_base( + nodes: ChildNodeArray, + name: Name, + attributes: { [key: string]: boolean }, + create_element: (name: Name) => Map[Name] +): Map[Name] + +function claim_element_base( + nodes: ChildNodeArray, + name: Name, + attributes: { [key: string]: boolean }, + create_element: (name: Name) => Map[Name] +): Map[Name] + +function claim_element_base( + nodes: ChildNodeArray, + name: Name, + attributes: { [key: string]: boolean }, + create_element: (name: Name) => Map[Name] +) { + return claim_node( nodes, - (node): node is Element | SVGElement => node.nodeName === name, + (node): node is Map[Name] => node.nodeName === name, (node) => { const remove = []; for (let j = 0; j < node.attributes.length; j++) { @@ -495,11 +497,11 @@ function claim_element_base(nodes: ChildNodeArray, name: string, attributes: { [ ); } -export function claim_element(nodes: ChildNodeArray, name: string, attributes: { [key: string]: boolean }) { +export function claim_element(nodes: ChildNodeArray, name: N, attributes: { [key: string]: boolean }) { return claim_element_base(nodes, name, attributes, element); } -export function claim_svg_element(nodes: ChildNodeArray, name: string, attributes: { [key: string]: boolean }) { +export function claim_svg_element(nodes: ChildNodeArray, name: N, attributes: { [key: string]: boolean }) { return claim_element_base(nodes, name, attributes, svg_element); } @@ -745,7 +747,6 @@ export class HtmlTag { this.c(html); } - //@ts-ignore this.i(anchor); } diff --git a/src/runtime/internal/utils.ts b/src/runtime/internal/utils.ts index 01cf1e416923..4351fe72cb90 100644 --- a/src/runtime/internal/utils.ts +++ b/src/runtime/internal/utils.ts @@ -1,5 +1,6 @@ import { Readable, Subscriber, Writable } from 'svelte/store'; import { SvelteComponent } from '../index.js'; +import { T$$ } from './types.js'; export function noop() {} @@ -17,8 +18,8 @@ export function is_promise(value: any): value is PromiseLike { return !!value && (typeof value === 'object' || typeof value === 'function') && typeof value.then === 'function'; } -export function add_location(element, file, line, column, char) { - element.__svelte_meta = { +export function add_location(element: Element, file: string | undefined, line: number, column: number, char: number) { + (element as Element & { __svelte_meta: any }).__svelte_meta = { loc: { file, line, column, char } }; } @@ -86,20 +87,22 @@ export function component_subscribe>(component: SvelteC component.$$.on_destroy.push(subscribe(store, callback)); } -export function create_slot(definition, ctx, $$scope, fn: Function) { +// TODO: find out what types are expected here instead of using `any` everywhere + +export function create_slot(definition: any[], ctx: any[], $$scope: T$$, fn: Function) { if (definition) { const slot_ctx = get_slot_context(definition, ctx, $$scope, fn); return definition[0](slot_ctx); } } -function get_slot_context(definition, ctx, $$scope, fn: Function) { +function get_slot_context(definition: any[], ctx: any[], $$scope: T$$, fn: Function) { return definition[1] && fn ? assign($$scope.ctx.slice(), definition[1](fn(ctx))) : $$scope.ctx; } -export function get_slot_changes(definition, $$scope, dirty, fn: Function) { +export function get_slot_changes(definition: any[], $$scope: T$$, dirty: number[], fn: Function) { if (definition[2] && fn) { const lets = definition[2](fn(dirty)); @@ -117,25 +120,26 @@ export function get_slot_changes(definition, $$scope, dirty, fn: Function) { return merged; } + // @ts-expect-error TODO return $$scope.dirty | lets; } return $$scope.dirty; } -export function update_slot_base(slot, slot_definition, ctx, $$scope, slot_changes, get_slot_context_fn) { +export function update_slot_base(slot: any, slot_definition: any[], ctx: any[], $$scope: T$$, slot_changes: any, get_slot_context_fn: Function) { if (slot_changes) { const slot_context = get_slot_context(slot_definition, ctx, $$scope, get_slot_context_fn); slot.p(slot_context, slot_changes); } } -export function update_slot(slot, slot_definition, ctx, $$scope, dirty, get_slot_changes_fn, get_slot_context_fn) { +export function update_slot(slot: any, slot_definition: any[], ctx: any[], $$scope: T$$, dirty: number[], get_slot_changes_fn: Function, get_slot_context_fn: Function) { const slot_changes = get_slot_changes(slot_definition, $$scope, dirty, get_slot_changes_fn); update_slot_base(slot, slot_definition, ctx, $$scope, slot_changes, get_slot_context_fn); } -export function get_all_dirty_from_scope($$scope) { +export function get_all_dirty_from_scope($$scope: T$$) { if ($$scope.ctx.length > 32) { const dirty = []; const length = $$scope.ctx.length / 32; @@ -190,6 +194,6 @@ export function has_prop(obj: X, prop: Y): return Object.prototype.hasOwnProperty.call(obj, prop); } -export function action_destroyer(action_result) { +export function action_destroyer(action_result: { destroy?: () => void } | undefined) { return action_result && is_function(action_result.destroy) ? action_result.destroy : noop; } diff --git a/tsconfig.json b/tsconfig.json index 56be920ecb06..39532bfe48f1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -25,7 +25,7 @@ "stripInternal": true, // TODO: error all the things - // "strict": true, + //"strict": true, "noImplicitThis": true, "noUnusedLocals": true, "noUnusedParameters": true, From 4c7b2d9d6e84229d20e642d133d38ba71562023d Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Tue, 11 Apr 2023 16:04:21 +0200 Subject: [PATCH 12/13] bring back some of the typings --- src/runtime/internal/dom.ts | 70 +++++++++++++++++++++++-------------- 1 file changed, 44 insertions(+), 26 deletions(-) diff --git a/src/runtime/internal/dom.ts b/src/runtime/internal/dom.ts index f001b90fd6a0..ffcc8cad4736 100644 --- a/src/runtime/internal/dom.ts +++ b/src/runtime/internal/dom.ts @@ -1,5 +1,32 @@ import { ResizeObserverSingleton } from './ResizeObserverSingleton'; import { contenteditable_truthy_values, has_prop } from './utils'; +import { Fragment } from './types.js'; + +// marks a part in the code where types +// 1. should be improved or +// 2. where casting is needed in order to satisfy TypeScript +// a deeper look at these parts is needed to check if they can be replaced with a normal cast or if they currently contain a potential bug +type TODO = T + +type NodeEx = Node & { + claim_order?: number, + hydrate_init?: true, + actual_end_child?: NodeEx, + childNodes: NodeListOf, +} + +type HTMLInputElementEx = HTMLInputElement & { + __value: string +} + +type HTMLSelectElementEx = HTMLSelectElement & { + __value: string + options: HTMLOptionElementEx[] +} + +type HTMLOptionElementEx = HTMLOptionElement & { + __value: string +} // Track which nodes are claimed during hydration. Unclaimed nodes can then be removed from the DOM // at the end of hydration without touching the remaining nodes. @@ -12,13 +39,6 @@ export function end_hydrating() { is_hydrating = false; } -type NodeEx = Node & { - claim_order?: number, - hydrate_init?: true, - actual_end_child?: NodeEx, - childNodes: NodeListOf, -}; - function upper_bound(low: number, high: number, key: (index: number) => number, value: number) { // Return first index of value larger than input value in the range [low, high) while (low < high) { @@ -264,38 +284,37 @@ export function listen(node: EventTarget, event: string, handler: EventListenerO return () => node.removeEventListener(event, handler, options); } -export function prevent_default(fn) { - return function (event) { +export function prevent_default(fn: (event: E) => void) { + return function(this: unknown, event: E) { event.preventDefault(); return fn.call(this, event); }; } -export function stop_propagation(fn) { - return function (event) { +export function stop_propagation(fn: (event: E) => void) { + return function(this: unknown, event: E) { event.stopPropagation(); return fn.call(this, event); }; } -export function stop_immediate_propagation(fn) { - return function (event) { +export function stop_immediate_propagation(fn: (event: E) => void) { + return function (this: unknown, event: E) { event.stopImmediatePropagation(); // @ts-ignore return fn.call(this, event); }; } -export function self(fn) { - return function (event) { +export function self(fn: (event: E) => void) { + return function(this: unknown, event: E) { // @ts-ignore if (event.target === this) fn.call(this, event); }; } -export function trusted(fn) { - return function (event) { - // @ts-ignore +export function trusted(fn: (event: E) => void) { + return function(this: unknown, event: E) { if (event.isTrusted) fn.call(this, event); }; } @@ -356,7 +375,7 @@ export function set_dynamic_element_data(tag: string) { return (/-/.test(tag)) ? set_custom_element_data_map : set_attributes; } -export function xlink_attr(node, attribute, value) { +export function xlink_attr(node: Element, attribute: string, value: string) { node.setAttributeNS('http://www.w3.org/1999/xlink', attribute, value); } @@ -418,7 +437,7 @@ export function init_binding_group_dynamic(group, indexes: number[]) { }; } -export function to_number(value) { +export function to_number(value: string | number) { return value === '' ? null : +value; } @@ -596,7 +615,7 @@ export function claim_comment(nodes:ChildNodeArray, data) { ); } -function find_comment(nodes, text, start) { +function find_comment(nodes: ChildNodeArray, text: string, start: number) { for (let i = start; i < nodes.length; i += 1) { const node = nodes[i]; if (node.nodeType === 8 /* comment node */ && (node.textContent as TODO).trim() === text) { @@ -606,7 +625,6 @@ function find_comment(nodes, text, start) { return nodes.length; } - export function claim_html_tag(nodes: ChildNodeArray, is_svg: boolean) { // find html opening tag const start_index = find_comment(nodes, 'HTML_TAG_START', 0); @@ -669,7 +687,7 @@ export function set_style(node: HTMLElement, key: string, value: string, importa } } -export function select_option(select, value, mounting) { +export function select_option(select: HTMLSelectElementEx, value: string, mounting: boolean) { for (let i = 0; i < select.options.length; i += 1) { const option = select.options[i]; @@ -691,9 +709,9 @@ export function select_options(select: HTMLSelectElementEx, value: string) { } } -export function select_value(select) { +export function select_value(select: HTMLSelectElementEx) { const selected_option = select.querySelector(':checked'); - return selected_option && selected_option.__value; + return selected_option && (selected_option as any).__value; } export function select_multiple_value(select: HTMLSelectElement) { @@ -773,7 +791,7 @@ export const resize_observer_border_box = new ResizeObserverSingleton({ box: 'bo export const resize_observer_device_pixel_content_box = new ResizeObserverSingleton({ box: 'device-pixel-content-box' }); export { ResizeObserverSingleton }; -export function toggle_class(element, name, toggle) { +export function toggle_class(element: HTMLElement, name: string, toggle: true) { element.classList[toggle ? 'add' : 'remove'](name); } From 06608737a8db2034d98a096437b0627ab20bcfda Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Tue, 11 Apr 2023 16:10:43 +0200 Subject: [PATCH 13/13] revert some changes --- src/runtime/{ambient.d.ts => ambient.ts} | 0 src/runtime/index.ts | 2 +- src/runtime/internal/utils.ts | 3 --- 3 files changed, 1 insertion(+), 4 deletions(-) rename src/runtime/{ambient.d.ts => ambient.ts} (100%) diff --git a/src/runtime/ambient.d.ts b/src/runtime/ambient.ts similarity index 100% rename from src/runtime/ambient.d.ts rename to src/runtime/ambient.ts diff --git a/src/runtime/index.ts b/src/runtime/index.ts index 91b10537bbae..2029f67a0464 100644 --- a/src/runtime/index.ts +++ b/src/runtime/index.ts @@ -1,4 +1,4 @@ -import './ambient.d.ts'; +import './ambient'; export { onMount, diff --git a/src/runtime/internal/utils.ts b/src/runtime/internal/utils.ts index ff0b0e4c6978..3996f1fe00eb 100644 --- a/src/runtime/internal/utils.ts +++ b/src/runtime/internal/utils.ts @@ -72,7 +72,6 @@ export function subscribe>(store: S, ...callbacks: Arra if (store == null) { return noop; } - // TODO: `store` does not accept an array of callbacks const unsub: any = store.subscribe(...(callbacks as [any])); return unsub.unsubscribe ? () => unsub.unsubscribe() : unsub; } @@ -87,8 +86,6 @@ export function component_subscribe>(component: SvelteC component.$$.on_destroy.push(subscribe(store, callback)); } -// TODO: find out what types are expected here instead of using `any` everywhere - export function create_slot(definition: any[], ctx: any[], $$scope: T$$, fn: Function) { if (definition) { const slot_ctx = get_slot_context(definition, ctx, $$scope, fn);