chore:Update for TS7 (#16485)

* Update for TS7

I am testing Typescript 7's JS support, which I've largely rewritten
during the switch to Go. That means Javascript code will need to change
much more than Typescript code. Fortunately, most of the changes are for
the better: Javascript semantics are now nearly identical to Typescript
semantics. It's much stricter and no longer has some persistent bugs
that arose from shady JS handling I wrote years ago.

This PR changes Svelte so that it compiles both with TS5.* and TS7,
which means that occasionally there are duplicative or non-obvious
changes. I'll annotate the interesting changes to explain why I made them.

Because TS7 is quite a way off, I don't know whether you'll want to take
this PR. Most of the changes are for the better, because they're due to
stricter TS-aligned checking. But some are neutral and there is the
previously mentioned duplication in a few places.

* add changeset

* revert scribbles mistakenly added

* pnpm run format

* revert mistaken edit

* return to function declarations w/type tag

* add job to ci.yml

* skipLibCheck for now

* no need for a changeset here

---------

Co-authored-by: Rich Harris <rich.harris@vercel.com>
Co-authored-by: Rich Harris <hello@rich-harris.dev>
pull/16570/head
Nathan Shively-Sanders 1 month ago committed by GitHub
parent 9efe672df2
commit 97f263c3d4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -60,6 +60,23 @@ jobs:
env: env:
CI: true CI: true
SVELTE_NO_ASYNC: true SVELTE_NO_ASYNC: true
TSGo:
permissions: {}
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 24
cache: pnpm
- name: install
run: pnpm install --frozen-lockfile
- name: install tsgo
run: cd packages/svelte && pnpm i -D @typescript/native-preview
- name: type check
run: cd packages/svelte && pnpm check:tsgo
Lint: Lint:
permissions: {} permissions: {}
runs-on: ubuntu-latest runs-on: ubuntu-latest

@ -141,6 +141,7 @@
"build": "node scripts/process-messages && rollup -c && pnpm generate:types && node scripts/check-treeshakeability.js", "build": "node scripts/process-messages && rollup -c && pnpm generate:types && node scripts/check-treeshakeability.js",
"dev": "node scripts/process-messages -w & rollup -cw", "dev": "node scripts/process-messages -w & rollup -cw",
"check": "tsc --project tsconfig.runtime.json && tsc && cd ./tests/types && tsc", "check": "tsc --project tsconfig.runtime.json && tsc && cd ./tests/types && tsc",
"check:tsgo": "tsgo --project tsconfig.runtime.json --skipLibCheck && tsgo --skipLibCheck",
"check:watch": "tsc --watch", "check:watch": "tsc --watch",
"generate:version": "node ./scripts/generate-version.js", "generate:version": "node ./scripts/generate-version.js",
"generate:types": "node ./scripts/generate-types.js && tsc -p tsconfig.generated.json", "generate:types": "node ./scripts/generate-types.js && tsc -p tsconfig.generated.json",

@ -26,9 +26,11 @@ await createBundle({
// so that types/properties with `@internal` (and its dependencies) are removed from the output // so that types/properties with `@internal` (and its dependencies) are removed from the output
stripInternal: true, stripInternal: true,
paths: Object.fromEntries( paths: Object.fromEntries(
Object.entries(pkg.imports).map(([key, value]) => { Object.entries(pkg.imports).map(
/** @param {[string,any]} import */ ([key, value]) => {
return [key, [value.types ?? value.default ?? value]]; return [key, [value.types ?? value.default ?? value]];
}) }
)
) )
}, },
modules: { modules: {

@ -55,7 +55,9 @@ export function convert(source, ast) {
// Insert svelte:options back into the root nodes // Insert svelte:options back into the root nodes
if (/** @type {any} */ (options)?.__raw__) { 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) { if (idx === -1) {
idx = node.fragment.nodes.length; idx = node.fragment.nodes.length;
} }

@ -9,8 +9,8 @@ import {
import { regex_ends_with_whitespace, regex_starts_with_whitespace } from '../../patterns.js'; import { regex_ends_with_whitespace, regex_starts_with_whitespace } from '../../patterns.js';
import { get_attribute_chunks, is_text_attribute } from '../../../utils/ast.js'; import { get_attribute_chunks, is_text_attribute } from '../../../utils/ast.js';
/** @typedef {NODE_PROBABLY_EXISTS | NODE_DEFINITELY_EXISTS} NodeExistsValue */ /** @typedef {typeof NODE_PROBABLY_EXISTS | typeof NODE_DEFINITELY_EXISTS} NodeExistsValue */
/** @typedef {FORWARD | BACKWARD} Direction */ /** @typedef {typeof FORWARD | typeof BACKWARD} Direction */
const NODE_PROBABLY_EXISTS = 0; const NODE_PROBABLY_EXISTS = 0;
const NODE_DEFINITELY_EXISTS = 1; const NODE_DEFINITELY_EXISTS = 1;

@ -297,7 +297,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, // 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 // 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), ast_type: /** @type {any} */ (null),
component_slots: new Set(), component_slots: /** @type {Set<string>} */ (new Set()),
expression: null, expression: null,
function_depth: 0, function_depth: 0,
has_props_rune: false, has_props_rune: false,

@ -14,6 +14,7 @@ export default function check_graph_for_cycles(edges) {
}, new Map()); }, new Map());
const visited = new Set(); const visited = new Set();
/** @type {Set<T>} */
const on_stack = new Set(); const on_stack = new Set();
/** @type {Array<Array<T>>} */ /** @type {Array<Array<T>>} */
const cycles = []; const cycles = [];

@ -599,7 +599,7 @@ function has_disabled_attribute(attribute_map) {
/** /**
* @param {string} tag_name * @param {string} tag_name
* @param {Map<string, AST.Attribute>} attribute_map * @param {Map<string, AST.Attribute>} attribute_map
* @returns {ElementInteractivity[keyof ElementInteractivity]} * @returns {typeof ElementInteractivity[keyof typeof ElementInteractivity]}
*/ */
function element_interactivity(tag_name, attribute_map) { function element_interactivity(tag_name, attribute_map) {
if ( if (

@ -145,6 +145,7 @@ export function visit_component(node, context) {
if (slot_name !== 'default') comments = []; if (slot_name !== 'default') comments = [];
} }
/** @type {Set<string>} */
const component_slots = new Set(); const component_slots = new Set();
for (const slot_name in nodes) { for (const slot_name in nodes) {

@ -22,7 +22,7 @@ const NUMBER = Symbol('number');
const STRING = Symbol('string'); const STRING = Symbol('string');
const FUNCTION = 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 = { const globals = {
BigInt: [NUMBER], BigInt: [NUMBER],
'Math.min': [NUMBER, Math.min], 'Math.min': [NUMBER, Math.min],

@ -87,7 +87,7 @@ export function pop_ignore() {
/** /**
* @param {AST.SvelteNode | NodeLike} node * @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 * @returns
*/ */
export function is_ignored(node, code) { export function is_ignored(node, code) {

@ -160,10 +160,14 @@ export function createEventDispatcher() {
e.lifecycle_outside_component('createEventDispatcher'); e.lifecycle_outside_component('createEventDispatcher');
} }
/**
* @param [detail]
* @param [options]
*/
return (type, detail, options) => { return (type, detail, options) => {
const events = /** @type {Record<string, Function | Function[]>} */ ( const events = /** @type {Record<string, Function | Function[]>} */ (
active_component_context.s.$$events active_component_context.s.$$events
)?.[/** @type {any} */ (type)]; )?.[/** @type {string} */ (type)];
if (events) { if (events) {
const callbacks = is_array(events) ? events.slice() : [events]; const callbacks = is_array(events) ? events.slice() : [events];

@ -64,7 +64,7 @@ export function hmr(original, get_source) {
// @ts-expect-error // @ts-expect-error
wrapper[FILENAME] = original[FILENAME]; wrapper[FILENAME] = original[FILENAME];
// @ts-expect-error // @ts-ignore
wrapper[HMR] = { wrapper[HMR] = {
// When we accept an update, we set the original source to the new component // When we accept an update, we set the original source to the new component
original, original,

@ -28,6 +28,8 @@ const PENDING = 0;
const THEN = 1; const THEN = 1;
const CATCH = 2; const CATCH = 2;
/** @typedef {typeof PENDING | typeof THEN | typeof CATCH} AwaitState */
/** /**
* @template V * @template V
* @param {TemplateNode} node * @param {TemplateNode} node
@ -67,9 +69,8 @@ export function await_block(node, get_input, pending_fn, then_fn, catch_fn) {
: mutable_source(/** @type {V} */ (undefined), false, false); : mutable_source(/** @type {V} */ (undefined), false, false);
var error_source = runes ? source(undefined) : mutable_source(undefined, false, false); var error_source = runes ? source(undefined) : mutable_source(undefined, false, false);
var resolved = false; var resolved = false;
/** /**
* @param {PENDING | THEN | CATCH} state * @param {AwaitState} state
* @param {boolean} restore * @param {boolean} restore
*/ */
function update(state, restore) { function update(state, restore) {

@ -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 // store a reference to the effect so that we can update the start/end nodes in reconciliation
each_effect ??= /** @type {Effect} */ (active_effect); each_effect ??= /** @type {Effect} */ (active_effect);
array = get(each_array); array = /** @type {V[]} */ (get(each_array));
var length = array.length; var length = array.length;
if (was_empty && length === 0) { if (was_empty && length === 0) {

@ -36,7 +36,7 @@ export function if_block(node, fn, elseif = false) {
/** @type {Effect | null} */ /** @type {Effect | null} */
var alternate_effect = null; var alternate_effect = null;
/** @type {UNINITIALIZED | boolean | null} */ /** @type {typeof UNINITIALIZED | boolean | null} */
var condition = UNINITIALIZED; var condition = UNINITIALIZED;
var flags = elseif ? EFFECT_TRANSPARENT : 0; var flags = elseif ? EFFECT_TRANSPARENT : 0;

@ -245,6 +245,7 @@ export function bind_checked(input, get, set = get) {
* @returns {V[]} * @returns {V[]}
*/ */
function get_binding_group_value(group, __value, checked) { function get_binding_group_value(group, __value, checked) {
/** @type {Set<V>} */
var value = new Set(); var value = new Set();
for (var i = 0; i < group.length; i += 1) { for (var i = 0; i < group.length; i += 1) {

@ -156,7 +156,7 @@ export function from_mathml(content, flags) {
/** /**
* @param {TemplateStructure[]} structure * @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) { function fragment_from_tree(structure, ns) {
var fragment = create_fragment(); var fragment = create_fragment();

@ -184,8 +184,7 @@ export function legacy_rest_props(props, exclude) {
* The proxy handler for spread props. Handles the incoming array of props * The proxy handler for spread props. Handles the incoming array of props
* that looks like `() => { dynamic: props }, { static: prop }, ..` and wraps * that looks like `() => { dynamic: props }, { static: prop }, ..` and wraps
* them so that the whole thing is passed to the component as the `$$props` argument. * 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<Record<string | symbol, unknown> | (() => Record<string | symbol, unknown>)> }>}}
* @type {ProxyHandler<{ props: Array<T | (() => T)> }>}}
*/ */
const spread_props_handler = { const spread_props_handler = {
get(target, key) { get(target, key) {
@ -362,8 +361,8 @@ export function prop(props, key, flags, fallback) {
// means we can just call `$$props.foo = value` directly // means we can just call `$$props.foo = value` directly
if (setter) { if (setter) {
var legacy_parent = props.$$legacy; var legacy_parent = props.$$legacy;
return /** @type {() => V} */ (
return function (/** @type {any} */ value, /** @type {boolean} */ mutation) { function (/** @type {V} */ value, /** @type {boolean} */ mutation) {
if (arguments.length > 0) { if (arguments.length > 0) {
// We don't want to notify if the value was mutated and the parent is in runes mode. // 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. // In that case the state proxy (if it exists) should take care of the notification.
@ -377,7 +376,8 @@ export function prop(props, key, flags, fallback) {
} }
return getter(); return getter();
}; }
);
} }
// Either prop is written to, but there's no binding, which means we // Either prop is written to, but there's no binding, which means we
@ -400,7 +400,8 @@ export function prop(props, key, flags, fallback) {
var parent_effect = /** @type {Effect} */ (active_effect); 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) { if (arguments.length > 0) {
const new_value = mutation ? get(d) : runes && bindable ? proxy(value) : value; const new_value = mutation ? get(d) : runes && bindable ? proxy(value) : value;
@ -424,5 +425,6 @@ export function prop(props, key, flags, fallback) {
} }
return get(d); return get(d);
}; }
);
} }

@ -37,6 +37,7 @@ import { Batch, schedule_effect } from './batch.js';
import { proxy } from '../proxy.js'; import { proxy } from '../proxy.js';
import { execute_derived } from './deriveds.js'; import { execute_derived } from './deriveds.js';
/** @type {Set<any>} */
export let inspect_effects = new Set(); export let inspect_effects = new Set();
/** @type {Map<Source, any>} */ /** @type {Map<Source, any>} */

@ -177,6 +177,7 @@ const document_listeners = new Map();
function _mount(Component, { target, anchor, props = {}, events, context, intro = true }) { function _mount(Component, { target, anchor, props = {}, events, context, intro = true }) {
init_operations(); init_operations();
/** @type {Set<string>} */
var registered_events = new Set(); var registered_events = new Set();
/** @param {Array<string>} events */ /** @param {Array<string>} events */

@ -284,7 +284,8 @@ export function update_reaction(reaction) {
try { try {
reaction.f |= REACTION_IS_UPDATING; reaction.f |= REACTION_IS_UPDATING;
var result = /** @type {Function} */ (0, reaction.fn)(); var fn = /** @type {Function} */ (reaction.fn);
var result = fn();
var deps = reaction.deps; var deps = reaction.deps;
if (new_deps !== null) { if (new_deps !== null) {

@ -6,7 +6,12 @@ export class HeadPayload {
uid = () => ''; uid = () => '';
title = ''; 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.css = css;
this.out = out; this.out = out;
this.title = title; this.title = title;

@ -35,7 +35,7 @@ export function validate_store(store, name) {
} }
/** /**
* @template {() => unknown} T * @template {(...args: any[]) => unknown} T
* @param {T} fn * @param {T} fn
*/ */
export function prevent_snippet_stringification(fn) { export function prevent_snippet_stringification(fn) {

@ -452,7 +452,7 @@ const RUNES = /** @type {const} */ ([
'$host' '$host'
]); ]);
/** @typedef {RUNES[number]} RuneName */ /** @typedef {typeof RUNES[number]} RuneName */
/** /**
* @param {string} name * @param {string} name
@ -462,7 +462,7 @@ export function is_rune(name) {
return RUNES.includes(/** @type {RuneName} */ (name)); return RUNES.includes(/** @type {RuneName} */ (name));
} }
/** @typedef {STATE_CREATION_RUNES[number]} StateCreationRuneName */ /** @typedef {typeof STATE_CREATION_RUNES[number]} StateCreationRuneName */
/** /**
* @param {string} name * @param {string} name
@ -477,7 +477,7 @@ const RAW_TEXT_ELEMENTS = /** @type {const} */ (['textarea', 'script', 'style',
/** @param {string} name */ /** @param {string} name */
export function is_raw_text_element(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));
} }
/** /**

@ -3,7 +3,9 @@ import { raf as svelte_raf } from 'svelte/internal/client';
import { queue_micro_task } from '../src/internal/client/dom/task.js'; import { queue_micro_task } from '../src/internal/client/dom/task.js';
export const raf = { export const raf = {
/** @type {Set<Animation>} */
animations: new Set(), animations: new Set(),
/** @type {Set<(n: number) => void>} */
ticks: new Set(), ticks: new Set(),
tick, tick,
time: 0, time: 0,
@ -54,14 +56,24 @@ class Animation {
/** /**
* @param {HTMLElement} target * @param {HTMLElement} target
* @param {Keyframe[]} keyframes * @param {Keyframe[] | PropertyIndexedKeyframes | null} keyframes
* @param {{ duration: number, delay: number }} options * @param {number | KeyframeAnimationOptions | undefined} options
*/ */
constructor(target, keyframes, { duration, delay }) { constructor(target, keyframes, options) {
this.target = target; this.target = target;
this.#keyframes = keyframes; this.#keyframes = Array.isArray(keyframes) ? keyframes : [];
this.#duration = Math.round(duration); if (typeof options === 'number') {
this.#delay = delay ?? 0; 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(); this._update();
} }
@ -189,6 +201,7 @@ function interpolate(a, b, p) {
* @param {{duration: number, delay: number}} options * @param {{duration: number, delay: number}} options
* @returns {globalThis.Animation} * @returns {globalThis.Animation}
*/ */
// @ts-ignore
HTMLElement.prototype.animate = function (keyframes, options) { HTMLElement.prototype.animate = function (keyframes, options) {
const animation = new Animation(this, keyframes, options); const animation = new Animation(this, keyframes, options);
raf.animations.add(animation); raf.animations.add(animation);
@ -196,6 +209,7 @@ HTMLElement.prototype.animate = function (keyframes, options) {
return animation; return animation;
}; };
// @ts-ignore
HTMLElement.prototype.getAnimations = function () { HTMLElement.prototype.getAnimations = function () {
return Array.from(raf.animations).filter((animation) => animation.target === this); return Array.from(raf.animations).filter((animation) => animation.target === this);
}; };

@ -43,6 +43,7 @@ export function create_deferred() {
/** @param {any} [reason] */ /** @param {any} [reason] */
let reject = (reason) => {}; let reject = (reason) => {};
/** @type {Promise<any>} */
const promise = new Promise((f, r) => { const promise = new Promise((f, r) => {
resolve = f; resolve = f;
reject = r; reject = r;

@ -87,15 +87,17 @@ function normalize_html(window, html) {
/** @param {any} node */ /** @param {any} node */
function normalize_children(node) { function normalize_children(node) {
// sort attributes // sort attributes
const attributes = Array.from(node.attributes).sort((a, b) => { const attributes = Array.from(node.attributes).sort(
(/** @type {any} */ a, /** @type {any} */ b) => {
return a.name < b.name ? -1 : 1; return a.name < b.name ? -1 : 1;
}); }
);
attributes.forEach((attr) => { attributes.forEach((/** @type{any} */ attr) => {
node.removeAttribute(attr.name); node.removeAttribute(attr.name);
}); });
attributes.forEach((attr) => { attributes.forEach((/** @type{any} */ attr) => {
node.setAttribute(attr.name, attr.value); node.setAttribute(attr.name, attr.value);
}); });

Loading…
Cancel
Save