Skip to content

chore:Update for TS7 #16485

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
8 changes: 5 additions & 3 deletions packages/svelte/scripts/generate-types.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,11 @@ 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]) => {
return [key, [value.types ?? value.default ?? value]];
})
Object.entries(pkg.imports).map(
/** @param {[string,any]} import */ ([key, value]) => {
return [key, [value.types ?? value.default ?? value]];
}
)
)
},
modules: {
Expand Down
4 changes: 3 additions & 1 deletion packages/svelte/src/compiler/legacy.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@ 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
);
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
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
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
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
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
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
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
8 changes: 4 additions & 4 deletions packages/svelte/src/internal/client/reactivity/equality.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
/** @import { Equals } from '#client' */

/** @type {Equals} */
export function equals(value) {
export const equals = function (value) {

Choose a reason for hiding this comment

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

sigh, this is actually one of the few places I liked JSDoc more than TS -- the fact that there's no way to type function declarations in TS grinds my gears!

FWIW this actually is going to negatively impact us -- in our language tools, whenever you export function load from a +page file, we insert /** @type {Load} */ above it to provide you generated types without you having to explicitly import and use them. I'm sure we can find a workaround but it'll at least be somewhat more difficult now, as we can't just change function declarations to const {name} = () => {} as it's semantically different

Copy link
Author

Choose a reason for hiding this comment

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

It's possible to implement this feature in the right way but it's invasive (all functions have to have a property for the type, even in TS where there's no syntax for it). If I know a real customer is relying on it then maybe we should consider keeping it. @jakebailey and @DanielRosenwasser love the feature but the old implementation was such a bug farm and the usual use of it so redundant with contextual typing that I didn't think it was needed. I'm glad this guinea pig upgrade is turning up problems!

I think one workaround would be to split Load into its constituent param/return tags and insert that instead.

Choose a reason for hiding this comment

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

Yeah I think the most realistic solution we've thrown around in Discord is this transform:

export function load(args) {}
// becomes
export function load(args: LoadArgs): LoadReturn {}

it's just a lot more parsing and inserting for us 😆

return value === this.v;
}
};

/**
* @param {unknown} a
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);
}
};
70 changes: 36 additions & 34 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
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,22 +361,23 @@ 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) {
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.
// If the parent is not in runes mode, we need to notify on mutation, too, that the prop
// has changed because the parent will not be able to detect the change otherwise.
if (!runes || !mutation || legacy_parent || is_store_sub) {
/** @type {Function} */ (setter)(mutation ? getter() : value);
return /** @type {() => V} */ (
function (/** @type {V} */ value, /** @type {boolean} */ mutation) {
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.
// If the parent is not in runes mode, we need to notify on mutation, too, that the prop
// has changed because the parent will not be able to detect the change otherwise.
if (!runes || !mutation || legacy_parent || is_store_sub) {
/** @type {Function} */ (setter)(mutation ? getter() : value);
}

return value;
}

return value;
return getter();
}

return getter();
};
);
}

// Either prop is written to, but there's no binding, which means we
Expand All @@ -400,29 +400,31 @@ export function prop(props, key, flags, fallback) {

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

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

Choose a reason for hiding this comment

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

after running prettier, Hide Whitespace is basically required to review this PR

if (arguments.length > 0) {
const new_value = mutation ? get(d) : runes && bindable ? proxy(value) : value;

set(d, new_value);
overridden = true;

set(d, new_value);
overridden = true;
if (fallback_value !== undefined) {
fallback_value = new_value;
}

if (fallback_value !== undefined) {
fallback_value = new_value;
return value;
}

return value;
}
// special case — avoid recalculating the derived if we're in a
// teardown function and the prop was overridden locally, or the
// component was already destroyed (this latter part is necessary
// because `bind:this` can read props after the component has
// been destroyed. TODO simplify `bind:this`
if ((is_destroying_effect && overridden) || (parent_effect.f & DESTROYED) !== 0) {
return d.v;
}

// special case — avoid recalculating the derived if we're in a
// teardown function and the prop was overridden locally, or the
// component was already destroyed (this latter part is necessary
// because `bind:this` can read props after the component has
// been destroyed. TODO simplify `bind:this`
if ((is_destroying_effect && overridden) || (parent_effect.f & DESTROYED) !== 0) {
return d.v;
return get(d);
}

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
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.

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

if (new_deps !== null) {
Expand Down
7 changes: 6 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,12 @@ 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
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
Loading
Loading