Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/gorgeous-gorillas-fix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'svelte': patch
---

Update for TS7
2 changes: 1 addition & 1 deletion packages/svelte/scripts/generate-types.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ await createBundle({
// so that types/properties with `@internal` (and its dependencies) are removed from the output
stripInternal: true,
paths: Object.fromEntries(
Object.entries(pkg.imports).map(([key, value]) => {
Object.entries(pkg.imports).map(/** @param {[string,any]} import */([key, value]) => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pkg.imports: any and Object.entries now infers [string, unknown] instead of [string, any]. This is stricter, and what happens in .ts files, but requires an explicit param type.
This shows up quite a bit in this PR.

return [key, [value.types ?? value.default ?? value]];
})
)
Expand Down
2 changes: 1 addition & 1 deletion packages/svelte/src/compiler/legacy.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export function convert(source, ast) {

// Insert svelte:options back into the root nodes
if (/** @type {any} */ (options)?.__raw__) {
let idx = node.fragment.nodes.findIndex((node) => options.end <= node.start);
let idx = node.fragment.nodes.findIndex((node) => /** @type {any} */ (options).end <= node.start);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as before; options would be unknown in TS7 without this cast.

if (idx === -1) {
idx = node.fragment.nodes.length;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import {
import { regex_ends_with_whitespace, regex_starts_with_whitespace } from '../../patterns.js';
import { get_attribute_chunks, is_text_attribute } from '../../../utils/ast.js';

/** @typedef {NODE_PROBABLY_EXISTS | NODE_DEFINITELY_EXISTS} NodeExistsValue */
/** @typedef {FORWARD | BACKWARD} Direction */
/** @typedef {typeof NODE_PROBABLY_EXISTS | typeof NODE_DEFINITELY_EXISTS} NodeExistsValue */
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JS type annotations no longer insert typeof in places where it finds a value but expects a type. You have to do it explicitly, like in TS.

/** @typedef {typeof FORWARD | typeof BACKWARD} Direction */

const NODE_PROBABLY_EXISTS = 0;
const NODE_DEFINITELY_EXISTS = 1;
Expand Down
2 changes: 1 addition & 1 deletion packages/svelte/src/compiler/phases/2-analyze/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ export function analyze_module(source, options) {
// TODO the following are not needed for modules, but we have to pass them in order to avoid type error,
// and reducing the type would result in a lot of tedious type casts elsewhere - find a good solution one day
ast_type: /** @type {any} */ (null),
component_slots: new Set(),
component_slots: /** @type {Set<string>} */ (new Set()),
expression: null,
function_depth: 0,
has_props_rune: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export default function check_graph_for_cycles(edges) {
}, new Map());

const visited = new Set();
/** @type {Set<T>} */
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

new Set(): Set<unknown> now, not Set<any>, so you have to explicitly provide a type. I think I gave real types throughout this PR rather than Set<any>, so the typing ends up better than before.

const on_stack = new Set();
/** @type {Array<Array<T>>} */
const cycles = [];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -599,7 +599,7 @@ function has_disabled_attribute(attribute_map) {
/**
* @param {string} tag_name
* @param {Map<string, AST.Attribute>} attribute_map
* @returns {ElementInteractivity[keyof ElementInteractivity]}
* @returns {typeof ElementInteractivity[keyof typeof ElementInteractivity]}
*/
function element_interactivity(tag_name, attribute_map) {
if (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ export function visit_component(node, context) {
if (slot_name !== 'default') comments = [];
}

/** @type {Set<string>} */
const component_slots = new Set();

for (const slot_name in nodes) {
Expand Down
2 changes: 1 addition & 1 deletion packages/svelte/src/compiler/phases/scope.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const NUMBER = Symbol('number');
const STRING = Symbol('string');
const FUNCTION = Symbol('string');

/** @type {Record<string, [type: NUMBER | STRING | UNKNOWN, fn?: Function]>} */
/** @type {Record<string, [type: typeof NUMBER | typeof STRING | typeof UNKNOWN, fn?: Function]>} */
const globals = {
BigInt: [NUMBER],
'Math.min': [NUMBER, Math.min],
Expand Down
2 changes: 1 addition & 1 deletion packages/svelte/src/compiler/state.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ export function pop_ignore() {

/**
* @param {AST.SvelteNode | NodeLike} node
* @param {import('../constants.js').IGNORABLE_RUNTIME_WARNINGS[number]} code
* @param {typeof import('../constants.js').IGNORABLE_RUNTIME_WARNINGS[number]} code
* @returns
*/
export function is_ignored(node, code) {
Expand Down
6 changes: 5 additions & 1 deletion packages/svelte/src/index-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -160,10 +160,14 @@ export function createEventDispatcher() {
e.lifecycle_outside_component('createEventDispatcher');
}

/**
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same: signature-to-signature checking (like when you're checking that a returned function has the right type) is stricter in TS7, so you have to explicitly note that detail and options are optional.

I also improved the cast on type but I can't remember if that's actually necessary.

* @param [detail]
* @param [options]
*/
return (type, detail, options) => {
const events = /** @type {Record<string, Function | Function[]>} */ (
active_component_context.s.$$events
)?.[/** @type {any} */ (type)];
)?.[/** @type {string} */ (type)];

if (events) {
const callbacks = is_array(events) ? events.slice() : [events];
Expand Down
2 changes: 1 addition & 1 deletion packages/svelte/src/internal/client/dev/hmr.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export function hmr(original, get_source) {
// @ts-expect-error
wrapper[FILENAME] = original[FILENAME];

// @ts-expect-error
// @ts-ignore
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error being ignored here is bogus, a result of wonky JS checking that mistakenly causes a circularity. In TS7, this bug is fixed. If this were a complete upgrade to TS7, I would remove this line, but since I want it to compile on TS5 and TS7, I switched it to ts-ignore.

wrapper[HMR] = {
// When we accept an update, we set the original source to the new component
original,
Expand Down
5 changes: 3 additions & 2 deletions packages/svelte/src/internal/client/dom/blocks/await.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ const PENDING = 0;
const THEN = 1;
const CATCH = 2;

/** @typedef {typeof PENDING | typeof THEN | typeof CATCH} AwaitState */

/**
* @template V
* @param {TemplateNode} node
Expand Down Expand Up @@ -67,9 +69,8 @@ export function await_block(node, get_input, pending_fn, then_fn, catch_fn) {
: mutable_source(/** @type {V} */ (undefined), false, false);
var error_source = runes ? source(undefined) : mutable_source(undefined, false, false);
var resolved = false;

/**
* @param {PENDING | THEN | CATCH} state
* @param {AwaitState} state
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here; typeof PENDING causes a bogus circularity error in TS5, but not when extracted to a typedef.

* @param {boolean} restore
*/
function update(state, restore) {
Expand Down
2 changes: 1 addition & 1 deletion packages/svelte/src/internal/client/dom/blocks/each.js
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f
// store a reference to the effect so that we can update the start/end nodes in reconciliation
each_effect ??= /** @type {Effect} */ (active_effect);

array = get(each_array);
array = /** @type {V[]} */ (get(each_array));
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

each_array: Derived<unknown[]> in TS7 where it was Derived<any[]> in TS5. The cast changes the type to be assignable to array in both 5 and 7.

var length = array.length;

if (was_empty && length === 0) {
Expand Down
2 changes: 1 addition & 1 deletion packages/svelte/src/internal/client/dom/blocks/if.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export function if_block(node, fn, elseif = false) {
/** @type {Effect | null} */
var alternate_effect = null;

/** @type {UNINITIALIZED | boolean | null} */
/** @type {typeof UNINITIALIZED | boolean | null} */
var condition = UNINITIALIZED;

var flags = elseif ? EFFECT_TRANSPARENT : 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,7 @@ export function bind_checked(input, get, set = get) {
* @returns {V[]}
*/
function get_binding_group_value(group, __value, checked) {
/** @type {Set<V>} */
var value = new Set();

for (var i = 0; i < group.length; i += 1) {
Expand Down
2 changes: 1 addition & 1 deletion packages/svelte/src/internal/client/dom/template.js
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ export function from_mathml(content, flags) {

/**
* @param {TemplateStructure[]} structure
* @param {NAMESPACE_SVG | NAMESPACE_MATHML | undefined} [ns]
* @param {typeof NAMESPACE_SVG | typeof NAMESPACE_MATHML | undefined} [ns]
*/
function fragment_from_tree(structure, ns) {
var fragment = create_fragment();
Expand Down
4 changes: 2 additions & 2 deletions packages/svelte/src/internal/client/reactivity/equality.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/** @import { Equals } from '#client' */

/** @type {Equals} */
export function equals(value) {
export const equals = function(value) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@type no longer works on function declarations because there's no equivalent in TS. But it does work with variables initialised to function expressions because of contextual typing.

return value === this.v;
}

Expand All @@ -26,6 +26,6 @@ export function not_equal(a, b) {
}

/** @type {Equals} */
export function safe_equals(value) {
export const safe_equals = function(value) {
return !safe_not_equal(value, this.v);
}
14 changes: 6 additions & 8 deletions packages/svelte/src/internal/client/reactivity/props.js
Original file line number Diff line number Diff line change
Expand Up @@ -184,8 +184,7 @@ export function legacy_rest_props(props, exclude) {
* The proxy handler for spread props. Handles the incoming array of props
* that looks like `() => { dynamic: props }, { static: prop }, ..` and wraps
* them so that the whole thing is passed to the component as the `$$props` argument.
* @template {Record<string | symbol, unknown>} T
* @type {ProxyHandler<{ props: Array<T | (() => T)> }>}}
* @type {ProxyHandler<{ props: Array<Record<string | symbol, unknown> | (() => Record<string | symbol, unknown>)> }>}}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@template should never have worked on a object literal--they don't have type parameters. I chose to inline the type instead of extracting to a typedef. Let me know if you want it the other way round.

*/
const spread_props_handler = {
get(target, key) {
Expand Down Expand Up @@ -362,8 +361,7 @@ export function prop(props, key, flags, fallback) {
// means we can just call `$$props.foo = value` directly
if (setter) {
var legacy_parent = props.$$legacy;

return function (/** @type {any} */ value, /** @type {boolean} */ mutation) {
return /** @type {() => V} */ (function (/** @type {V} */ value, /** @type {boolean} */ mutation) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I could have made this a smaller change with judicious use of any, but I preferred better types instead of a smaller diff.

if (arguments.length > 0) {
// We don't want to notify if the value was mutated and the parent is in runes mode.
// In that case the state proxy (if it exists) should take care of the notification.
Expand All @@ -377,7 +375,7 @@ export function prop(props, key, flags, fallback) {
}

return getter();
};
});
}

// Either prop is written to, but there's no binding, which means we
Expand All @@ -399,8 +397,8 @@ export function prop(props, key, flags, fallback) {
if (bindable) get(d);

var parent_effect = /** @type {Effect} */ (active_effect);

return function (/** @type {any} */ value, /** @type {boolean} */ mutation) {
return /** @type {() => V} */(function (/** @type {any} */ value, /** @type {boolean} */ mutation) {
if (arguments.length > 0) {
const new_value = mutation ? get(d) : runes && bindable ? proxy(value) : value;

Expand All @@ -424,5 +422,5 @@ export function prop(props, key, flags, fallback) {
}

return get(d);
};
});
}
1 change: 1 addition & 0 deletions packages/svelte/src/internal/client/reactivity/sources.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import { Batch, schedule_effect } from './batch.js';
import { proxy } from '../proxy.js';
import { execute_derived } from './deriveds.js';

/** @type {Set<any>} */
export let inspect_effects = new Set();

/** @type {Map<Source, any>} */
Expand Down
1 change: 1 addition & 0 deletions packages/svelte/src/internal/client/render.js
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ const document_listeners = new Map();
function _mount(Component, { target, anchor, props = {}, events, context, intro = true }) {
init_operations();

/** @type {Set<string>} */
var registered_events = new Set();

/** @param {Array<string>} events */
Expand Down
3 changes: 2 additions & 1 deletion packages/svelte/src/internal/client/runtime.js
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,8 @@ export function update_reaction(reaction) {

try {
reaction.f |= REACTION_IS_UPDATING;
var result = /** @type {Function} */ (0, reaction.fn)();
var fn = /** @type {Function} */ (reaction.fn);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typescript errors on (0, reaction.fn)()--which is wrong, in my opinion--but in TS5 the cast syntax squelches this error for some reason. I split the line to avoid the comma expression but I'm not completely sure it's equivalent. It might be better to put a // @ts-ignore on the original line.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The (0, reaction.fn) trick is a common pattern to avoid this binding.

To me it looks more like maybe this was the intended type:

var result = (0, /** @type {Function} */ (reaction.fn))();

And this is an example why it matters:

const reaction = {
  location: "House",
  fn() {
    console.log("this", this);
    return `Hello from ${this.location}!`;
  }
};
// 'this' is acting on globalThis/window now
const result = (0, reaction.fn)();
// Hint: not "House"
console.log("result", result);

var result = fn();
var deps = reaction.deps;

if (new_deps !== null) {
Expand Down
3 changes: 2 additions & 1 deletion packages/svelte/src/internal/server/payload.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ export class HeadPayload {
uid = () => '';
title = '';

constructor(css = new Set(), /** @type {string[]} */ out = [], title = '', uid = () => '') {
constructor(/** @type {Set<{ hash: string; code: string }>} */ css = new Set(), /** @type {string[]} */ out = [], title = '', uid = () => '') {

this.css = css;
this.out = out;
this.title = title;
Expand Down
2 changes: 1 addition & 1 deletion packages/svelte/src/internal/shared/validate.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export function validate_store(store, name) {
}

/**
* @template {() => unknown} T
* @template {(...args: any[]) => unknown} T
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JS arity checking between signatures is lax in TS5 so it didn't catch this.

* @param {T} fn
*/
export function prevent_snippet_stringification(fn) {
Expand Down
6 changes: 3 additions & 3 deletions packages/svelte/src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -452,7 +452,7 @@ const RUNES = /** @type {const} */ ([
'$host'
]);

/** @typedef {RUNES[number]} RuneName */
/** @typedef {typeof RUNES[number]} RuneName */

/**
* @param {string} name
Expand All @@ -462,7 +462,7 @@ export function is_rune(name) {
return RUNES.includes(/** @type {RuneName} */ (name));
}

/** @typedef {STATE_CREATION_RUNES[number]} StateCreationRuneName */
/** @typedef {typeof STATE_CREATION_RUNES[number]} StateCreationRuneName */

/**
* @param {string} name
Expand All @@ -477,7 +477,7 @@ const RAW_TEXT_ELEMENTS = /** @type {const} */ (['textarea', 'script', 'style',

/** @param {string} name */
export function is_raw_text_element(name) {
return RAW_TEXT_ELEMENTS.includes(/** @type {RAW_TEXT_ELEMENTS[number]} */ (name));
return RAW_TEXT_ELEMENTS.includes(/** @type {typeof RAW_TEXT_ELEMENTS[number]} */ (name));
}

/**
Expand Down
26 changes: 20 additions & 6 deletions packages/svelte/tests/animation-helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import { raf as svelte_raf } from 'svelte/internal/client';
import { queue_micro_task } from '../src/internal/client/dom/task.js';

export const raf = {
/** @type {Set<Animation>} */
animations: new Set(),
/** @type {Set<(n: number) => void>} */
ticks: new Set(),
tick,
time: 0,
Expand Down Expand Up @@ -54,14 +56,24 @@ class Animation {

/**
* @param {HTMLElement} target
* @param {Keyframe[]} keyframes
* @param {{ duration: number, delay: number }} options
* @param {Keyframe[] | PropertyIndexedKeyframes | null} keyframes
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I kind of went overboard here fixing up this test to handle the DOM types. It's probably better to turn a bunch of these types to any instead. I needed to do something because of the improved signature arity checking in TS7.

* @param {number | KeyframeAnimationOptions | undefined} options
*/
constructor(target, keyframes, { duration, delay }) {
constructor(target, keyframes, options) {
this.target = target;
this.#keyframes = keyframes;
this.#duration = Math.round(duration);
this.#delay = delay ?? 0;
this.#keyframes = Array.isArray(keyframes) ? keyframes : [];
if (typeof options === 'number') {
this.#duration = options;
this.#delay = 0;
} else {
const { duration = 0, delay = 0 } = options ?? {};
if (typeof duration === 'object') {
this.#duration = 0;
} else {
this.#duration = Math.round(+duration);
}
this.#delay = delay;
}

this._update();
}
Expand Down Expand Up @@ -189,13 +201,15 @@ function interpolate(a, b, p) {
* @param {{duration: number, delay: number}} options
* @returns {globalThis.Animation}
*/
// @ts-ignore
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the location of the error to be ignored moved in TS7 to match the location reported in a .ts file

HTMLElement.prototype.animate = function (keyframes, options) {
const animation = new Animation(this, keyframes, options);
raf.animations.add(animation);
// @ts-ignore
return animation;
};

// @ts-ignore
HTMLElement.prototype.getAnimations = function () {
return Array.from(raf.animations).filter((animation) => animation.target === this);
};
1 change: 1 addition & 0 deletions packages/svelte/tests/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export function create_deferred() {
/** @param {any} [reason] */
let reject = (reason) => {};

/** @type {Promise<any>} */
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TS7 inferred promise: Promise<unknown> without an explicit annotation.

const promise = new Promise((f, r) => {
resolve = f;
reject = r;
Expand Down
Loading
Loading