From d4030302f102dfca35c86883611f0b6cc11082c8 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 24 Jun 2024 17:04:56 -0400 Subject: [PATCH 1/3] convert some .js files to .ts --- packages/repl/src/lib/Bundler.js | 72 -------- packages/repl/src/lib/Bundler.ts | 68 +++++++ .../lib/{autocomplete.js => autocomplete.ts} | 47 ++--- packages/repl/src/lib/context.js | 13 -- packages/repl/src/lib/context.ts | 12 ++ packages/repl/src/lib/{theme.js => theme.ts} | 11 +- packages/repl/src/lib/utils.js | 16 -- packages/repl/src/lib/utils.ts | 10 + .../workers/bundler/{index.js => index.ts} | 173 +++++++----------- .../plugins/{commonjs.js => commonjs.ts} | 9 +- .../bundler/plugins/{glsl.js => glsl.ts} | 7 +- .../bundler/plugins/{json.js => json.ts} | 7 +- .../{loop-protect.js => loop-protect.ts} | 57 +++--- .../plugins/{replace.js => replace.ts} | 34 ++-- .../workers/compiler/{index.js => index.ts} | 71 ++++--- packages/repl/src/lib/workers/jsconfig.json | 9 - .../{patch_window.js => patch_window.ts} | 0 packages/repl/src/routes/+page.svelte | 5 +- 18 files changed, 269 insertions(+), 352 deletions(-) delete mode 100644 packages/repl/src/lib/Bundler.js create mode 100644 packages/repl/src/lib/Bundler.ts rename packages/repl/src/lib/{autocomplete.js => autocomplete.ts} (85%) delete mode 100644 packages/repl/src/lib/context.js create mode 100644 packages/repl/src/lib/context.ts rename packages/repl/src/lib/{theme.js => theme.ts} (96%) delete mode 100644 packages/repl/src/lib/utils.js create mode 100644 packages/repl/src/lib/utils.ts rename packages/repl/src/lib/workers/bundler/{index.js => index.ts} (77%) rename packages/repl/src/lib/workers/bundler/plugins/{commonjs.js => commonjs.ts} (88%) rename packages/repl/src/lib/workers/bundler/plugins/{glsl.js => glsl.ts} (63%) rename packages/repl/src/lib/workers/bundler/plugins/{json.js => json.ts} (61%) rename packages/repl/src/lib/workers/bundler/plugins/{loop-protect.js => loop-protect.ts} (57%) rename packages/repl/src/lib/workers/bundler/plugins/{replace.js => replace.ts} (57%) rename packages/repl/src/lib/workers/compiler/{index.js => index.ts} (61%) delete mode 100644 packages/repl/src/lib/workers/jsconfig.json rename packages/repl/src/lib/workers/{patch_window.js => patch_window.ts} (100%) diff --git a/packages/repl/src/lib/Bundler.js b/packages/repl/src/lib/Bundler.js deleted file mode 100644 index e20946de7..000000000 --- a/packages/repl/src/lib/Bundler.js +++ /dev/null @@ -1,72 +0,0 @@ -import Worker from './workers/bundler/index.js?worker'; - -const workers = new Map(); - -let uid = 1; - -export default class Bundler { - /** @type {Worker} */ - worker; - - /** @param {{ packages_url: string; svelte_url: string; onstatus: (val: string | null) => void}} param0 */ - constructor({ packages_url, svelte_url, onstatus }) { - const hash = `${packages_url}:${svelte_url}`; - - if (!workers.has(hash)) { - const worker = new Worker(); - worker.postMessage({ type: 'init', packages_url, svelte_url }); - workers.set(hash, worker); - } - - this.worker = workers.get(hash); - - this.handlers = new Map(); - - this.worker.addEventListener( - 'message', - /** - * - * @param {MessageEvent} event - * @returns - */ - (event) => { - const handler = this.handlers.get(event.data.uid); - - if (handler) { - // if no handler, was meant for a different REPL - if (event.data.type === 'status') { - onstatus(event.data.message); - return; - } - - onstatus(null); - handler(event.data); - this.handlers.delete(event.data.uid); - } - } - ); - } - - /** - * - * @param {import('./types').File[]} files - * @returns - */ - bundle(files) { - return new Promise((fulfil) => { - this.handlers.set(uid, fulfil); - - this.worker.postMessage({ - uid, - type: 'bundle', - files - }); - - uid += 1; - }); - } - - destroy() { - this.worker.terminate(); - } -} diff --git a/packages/repl/src/lib/Bundler.ts b/packages/repl/src/lib/Bundler.ts new file mode 100644 index 000000000..5f0a7b707 --- /dev/null +++ b/packages/repl/src/lib/Bundler.ts @@ -0,0 +1,68 @@ +import type { File } from './types'; +import Worker from './workers/bundler/index.js?worker'; +import type { BundleMessageData } from './workers/workers'; + +const workers = new Map(); + +let uid = 1; + +export default class Bundler { + worker: Worker; + handlers: Map void>; + + constructor({ + packages_url, + svelte_url, + onstatus + }: { + packages_url: string; + svelte_url: string; + onstatus: (val: string | null) => void; + }) { + const hash = `${packages_url}:${svelte_url}`; + + if (!workers.has(hash)) { + const worker = new Worker(); + worker.postMessage({ type: 'init', packages_url, svelte_url }); + workers.set(hash, worker); + } + + this.worker = workers.get(hash); + + this.handlers = new Map(); + + this.worker.addEventListener('message', (event: MessageEvent) => { + const handler = this.handlers.get(event.data.uid); + + if (handler) { + // if no handler, was meant for a different REPL + if (event.data.type === 'status') { + onstatus(event.data.message); + return; + } + + onstatus(null); + handler(event.data); + this.handlers.delete(event.data.uid); + } + }); + } + + bundle(files: File[]) { + return new Promise((fulfil) => { + this.handlers.set(uid, fulfil); + + this.worker.postMessage({ + uid, + type: 'bundle', + files + }); + + uid += 1; + }); + } + + destroy() { + this.worker.terminate(); + } +} diff --git a/packages/repl/src/lib/autocomplete.js b/packages/repl/src/lib/autocomplete.ts similarity index 85% rename from packages/repl/src/lib/autocomplete.js rename to packages/repl/src/lib/autocomplete.ts index ff0c2f741..34ba6e746 100644 --- a/packages/repl/src/lib/autocomplete.js +++ b/packages/repl/src/lib/autocomplete.ts @@ -1,13 +1,14 @@ -import { snippetCompletion } from '@codemirror/autocomplete'; +import { CompletionContext, snippetCompletion } from '@codemirror/autocomplete'; import { syntaxTree } from '@codemirror/language'; +import type { SyntaxNode } from '@lezer/common'; +import type { File } from './types'; -/** @typedef {(node: import('@lezer/common').SyntaxNode, context: import('@codemirror/autocomplete').CompletionContext, selected: import('./types').File) => boolean} Test */ +interface Test { + (node: SyntaxNode, context: CompletionContext, selected: File): boolean; +} -/** - * Returns `true` if `$bindable()` is valid - * @type {Test} - */ -function is_bindable(node, context) { +/** Returns `true` if `$bindable()` is valid */ +const is_bindable: Test = (node, context) => { // disallow outside `let { x = $bindable }` if (node.parent?.name !== 'PatternProperty') return false; if (node.parent.parent?.name !== 'ObjectPattern') return false; @@ -32,14 +33,13 @@ function is_bindable(node, context) { last.firstChild?.name === 'VariableName' && context.state.sliceDoc(last.firstChild.from, last.firstChild.to) === '$props' ); -} +}; /** * Returns `true` if `$props()` is valid * TODO only allow in `.svelte` files, and only at the top level - * @type {Test} */ -function is_props(node, _, selected) { +const is_props: Test = (node, _, selected) => { if (selected.type !== 'svelte') return false; return ( @@ -47,13 +47,12 @@ function is_props(node, _, selected) { node.parent?.name === 'VariableDeclaration' && node.parent.parent?.name === 'Script' ); -} +}; /** * Returns `true` is this is a valid place to declare state - * @type {Test} */ -function is_state(node) { +const is_state: Test = (node) => { let parent = node.parent; if (node.name === '.' || node.name === 'PropertyName') { @@ -64,14 +63,13 @@ function is_state(node) { if (!parent) return false; return parent.name === 'VariableDeclaration' || parent.name === 'PropertyDeclaration'; -} +}; /** * Returns `true` if we're already in a valid call expression, e.g. * changing an existing `$state()` to `$state.frozen()` - * @type {Test} */ -function is_state_call(node) { +const is_state_call: Test = (node) => { let parent = node.parent; if (node.name === '.' || node.name === 'PropertyName') { @@ -87,10 +85,9 @@ function is_state_call(node) { if (!parent) return false; return parent.name === 'VariableDeclaration' || parent.name === 'PropertyDeclaration'; -} +}; -/** @type {Test} */ -function is_statement(node) { +const is_statement: Test = (node) => { if (node.name === 'VariableName') { return node.parent?.name === 'ExpressionStatement'; } @@ -100,10 +97,9 @@ function is_statement(node) { } return false; -} +}; -/** @type {Array<{ snippet: string, test?: Test }>} */ -const runes = [ +const runes: Array<{ snippet: string; test?: Test }> = [ { snippet: '$state(${})', test: is_state }, { snippet: '$state', test: is_state_call }, { snippet: '$props()', test: is_props }, @@ -132,12 +128,7 @@ const options = runes.map(({ snippet, test }, i) => ({ test })); -/** - * @param {import('@codemirror/autocomplete').CompletionContext} context - * @param {import('./types.js').File} selected - * @param {import('./types.js').File[]} files - */ -export function autocomplete(context, selected, files) { +export function autocomplete(context: CompletionContext, selected: File, files: File[]) { let node = syntaxTree(context.state).resolveInner(context.pos, -1); if (node.name === 'String' && node.parent?.name === 'ImportDeclaration') { diff --git a/packages/repl/src/lib/context.js b/packages/repl/src/lib/context.js deleted file mode 100644 index e983e6d14..000000000 --- a/packages/repl/src/lib/context.js +++ /dev/null @@ -1,13 +0,0 @@ -import { getContext, setContext } from 'svelte'; - -const key = Symbol('repl'); - -/** @returns {import("./types").ReplContext} */ -export function get_repl_context() { - return getContext(key); -} - -/** @param {import("./types").ReplContext} value */ -export function set_repl_context(value) { - setContext(key, value); -} diff --git a/packages/repl/src/lib/context.ts b/packages/repl/src/lib/context.ts new file mode 100644 index 000000000..6e0b95924 --- /dev/null +++ b/packages/repl/src/lib/context.ts @@ -0,0 +1,12 @@ +import { getContext, setContext } from 'svelte'; +import type { ReplContext } from './types'; + +const key = Symbol('repl'); + +export function get_repl_context(): ReplContext { + return getContext(key); +} + +export function set_repl_context(value: ReplContext) { + setContext(key, value); +} diff --git a/packages/repl/src/lib/theme.js b/packages/repl/src/lib/theme.ts similarity index 96% rename from packages/repl/src/lib/theme.js rename to packages/repl/src/lib/theme.ts index 867e144ac..3a6c4db49 100644 --- a/packages/repl/src/lib/theme.js +++ b/packages/repl/src/lib/theme.ts @@ -11,20 +11,13 @@ const WARNING_BG = `hsl(${WARNING_HUE} 100% 40% / 0.5)`; const ERROR_FG = `hsl(${ERROR_HUE} 100% 40%)`; const ERROR_BG = `hsl(${ERROR_HUE} 100% 40% / 0.5)`; -/** - * @param {string} content - * @param {string} attrs - */ -function svg(content, attrs = `viewBox="0 0 40 40"`) { +function svg(content: string, attrs = `viewBox="0 0 40 40"`) { return `url('data:image/svg+xml,${encodeURIComponent( content )}')`; } -/** - * @param {string} color - */ -function underline(color) { +function underline(color: string) { return svg( ``, `width="6" height="4"` diff --git a/packages/repl/src/lib/utils.js b/packages/repl/src/lib/utils.js deleted file mode 100644 index d378e1d51..000000000 --- a/packages/repl/src/lib/utils.js +++ /dev/null @@ -1,16 +0,0 @@ -/** - * @param {number} min - * @param {number} max - * @param {number} value - */ -export const clamp = (min, max, value) => Math.max(min, Math.min(max, value)); - -/** - * @param {number} ms - */ -export const sleep = (ms) => new Promise((f) => setTimeout(f, ms)); - -/** @param {import('./types').File} file */ -export function get_full_filename(file) { - return `${file.name}.${file.type}`; -} diff --git a/packages/repl/src/lib/utils.ts b/packages/repl/src/lib/utils.ts new file mode 100644 index 000000000..7d85b601c --- /dev/null +++ b/packages/repl/src/lib/utils.ts @@ -0,0 +1,10 @@ +import type { File } from './types'; + +export const clamp = (min: number, max: number, value: number) => + Math.max(min, Math.min(max, value)); + +export const sleep = (ms: number) => new Promise((f) => setTimeout(f, ms)); + +export function get_full_filename(file: File) { + return `${file.name}.${file.type}`; +} diff --git a/packages/repl/src/lib/workers/bundler/index.js b/packages/repl/src/lib/workers/bundler/index.ts similarity index 77% rename from packages/repl/src/lib/workers/bundler/index.js rename to packages/repl/src/lib/workers/bundler/index.ts index f0a846e11..86ce5f566 100644 --- a/packages/repl/src/lib/workers/bundler/index.js +++ b/packages/repl/src/lib/workers/bundler/index.ts @@ -1,7 +1,5 @@ -/// - import '../patch_window.js'; -import { sleep } from '$lib/utils.js'; +import { sleep } from '$lib/utils'; import { rollup } from '@rollup/browser'; import { DEV } from 'esm-env'; import * as resolve from 'resolve.exports'; @@ -10,78 +8,70 @@ import glsl from './plugins/glsl.js'; import json from './plugins/json.js'; import replace from './plugins/replace.js'; import loop_protect from './plugins/loop-protect.js'; +import type { Plugin, TransformResult } from '@rollup/browser'; +import type { BundleMessageData } from '../workers.js'; +import type { File, Warning } from '$lib/types.js'; +import type { CompileResult } from 'svelte/compiler'; -/** @type {string} */ -let packages_url; - -/** @type {string} */ -let svelte_url; - -/** @type {number} */ -let current_id; +let packages_url: string; +let svelte_url: string; +let current_id: number; -/** @type {(arg?: never) => void} */ -let fulfil_ready; +let fulfil_ready: (arg?: never) => void; const ready = new Promise((f) => { fulfil_ready = f; }); -self.addEventListener( - 'message', - /** @param {MessageEvent} event */ async (event) => { - switch (event.data.type) { - case 'init': { - ({ packages_url, svelte_url } = event.data); +self.addEventListener('message', async (event: MessageEvent) => { + switch (event.data.type) { + case 'init': { + ({ packages_url, svelte_url } = event.data); - const { version } = await fetch(`${svelte_url}/package.json`).then((r) => r.json()); - console.log(`Using Svelte compiler version ${version}`); + const { version } = await fetch(`${svelte_url}/package.json`).then((r) => r.json()); + console.log(`Using Svelte compiler version ${version}`); - const compiler = await fetch(`${svelte_url}/compiler/index.js`).then((r) => r.text()); - (0, eval)(compiler + '\n//# sourceURL=compiler/index.js@' + version); + const compiler = await fetch(`${svelte_url}/compiler/index.js`).then((r) => r.text()); + (0, eval)(compiler + '\n//# sourceURL=compiler/index.js@' + version); - fulfil_ready(); - break; - } + fulfil_ready(); + break; + } - case 'bundle': { - await ready; - const { uid, files } = event.data; + case 'bundle': { + await ready; + const { uid, files } = event.data; - if (files.length === 0) return; + if (files.length === 0) return; - current_id = uid; + current_id = uid; - setTimeout(async () => { - if (current_id !== uid) return; + setTimeout(async () => { + if (current_id !== uid) return; - const result = await bundle({ uid, files }); + const result = await bundle({ uid, files }); - if (JSON.stringify(result.error) === JSON.stringify(ABORT)) return; - if (result && uid === current_id) postMessage(result); - }); + if (JSON.stringify(result.error) === JSON.stringify(ABORT)) return; + if (result && uid === current_id) postMessage(result); + }); - break; - } + break; } } -); +}); -/** @type {Record<'client' | 'server', Map }>>} */ -let cached = { +let cached: Record< + 'client' | 'server', + Map }> +> = { client: new Map(), server: new Map() }; const ABORT = { aborted: true }; -/** @type {Map>} */ -const FETCH_CACHE = new Map(); +const FETCH_CACHE: Map> = new Map(); -/** - * @param {string} url - * @param {number} uid - */ -async function fetch_if_uncached(url, uid) { +async function fetch_if_uncached(url: string, uid: number) { if (FETCH_CACHE.has(url)) { return FETCH_CACHE.get(url); } @@ -108,23 +98,12 @@ async function fetch_if_uncached(url, uid) { return promise; } -/** - * @param {string} url - * @param {number} uid - */ -async function follow_redirects(url, uid) { +async function follow_redirects(url: string, uid: number) { const res = await fetch_if_uncached(url, uid); return res?.url; } -/** - * - * @param {number} major - * @param {number} minor - * @param {number} patch - * @returns {number} - */ -function compare_to_version(major, minor, patch) { +function compare_to_version(major: number, minor: number, patch: number): number { const v = svelte.VERSION.match(/^(\d+)\.(\d+)\.(\d+)/); // @ts-ignore @@ -147,14 +126,12 @@ function has_loopGuardTimeout_feature() { return compare_to_version(3, 14, 0) >= 0; } -/** - * - * @param {Record} pkg - * @param {string} subpath - * @param {number} uid - * @param {string} pkg_url_base - */ -async function resolve_from_pkg(pkg, subpath, uid, pkg_url_base) { +async function resolve_from_pkg( + pkg: Record, + subpath: string, + uid: number, + pkg_url_base: string +) { // match legacy Rollup logic — pkg.svelte takes priority over pkg.exports if (typeof pkg.svelte === 'string' && subpath === '.') { return pkg.svelte; @@ -218,30 +195,21 @@ async function resolve_from_pkg(pkg, subpath, uid, pkg_url_base) { return subpath; } -/** - * @param {number} uid - * @param {'client' | 'server'} mode - * @param {typeof cached['client']} cache - * @param {Map} local_files_lookup - */ -async function get_bundle(uid, mode, cache, local_files_lookup) { +async function get_bundle( + uid: number, + mode: 'client' | 'server', + cache: (typeof cached)['client'], + local_files_lookup: Map +) { let bundle; /** A set of package names (without subpaths) to include in pkg.devDependencies when downloading an app */ - /** @type {Set} */ - const imports = new Set(); - - /** @type {import('$lib/types.js').Warning[]} */ - const warnings = []; - - /** @type {{ message: string }[]} */ - const all_warnings = []; - - /** @type {typeof cache} */ - const new_cache = new Map(); + const imports: Set = new Set(); + const warnings: Warning[] = []; + const all_warnings: Array<{ message: string }> = []; + const new_cache: typeof cache = new Map(); - /** @type {import('@rollup/browser').Plugin} */ - const repl_plugin = { + const repl_plugin: Plugin = { name: 'svelte-repl', async resolveId(importee, importer) { if (uid !== current_id) throw ABORT; @@ -371,7 +339,7 @@ async function get_bundle(uid, mode, cache, local_files_lookup) { const name = id.split('/').pop()?.split('.')[0]; const cached_id = cache.get(id); - let result; + let result: CompileResult; if (cached_id && cached_id.code === code) { result = cached_id.result; @@ -416,8 +384,7 @@ async function get_bundle(uid, mode, cache, local_files_lookup) { warnings.push(warning); }); - /** @type {import('@rollup/browser').TransformResult} */ - const transform_result = { + const transform_result: TransformResult = { code: result.js.code, map: result.js.map }; @@ -462,18 +429,13 @@ async function get_bundle(uid, mode, cache, local_files_lookup) { } } -/** - * @param {{ uid: number; files: import('$lib/types.js').File[] }} param0 - * @returns - */ -async function bundle({ uid, files }) { +async function bundle({ uid, files }: { uid: number; files: File[] }) { if (!DEV) { console.clear(); console.log(`running Svelte compiler version %c${svelte.VERSION}`, 'font-weight: bold'); } - /** @type {Map} */ - const lookup = new Map(); + const lookup: Map = new Map(); lookup.set('./__entry.js', { name: '__entry', @@ -490,8 +452,12 @@ async function bundle({ uid, files }) { lookup.set(path, file); }); - /** @type {Awaited>} */ - let client = await get_bundle(uid, 'client', cached.client, lookup); + let client: Awaited> = await get_bundle( + uid, + 'client', + cached.client, + lookup + ); let error; try { @@ -542,9 +508,8 @@ async function bundle({ uid, files }) { } catch (err) { console.error(err); - /** @type {Error} */ // @ts-ignore - const e = error || err; + const e: Error = error || err; // @ts-ignore delete e.toString; diff --git a/packages/repl/src/lib/workers/bundler/plugins/commonjs.js b/packages/repl/src/lib/workers/bundler/plugins/commonjs.ts similarity index 88% rename from packages/repl/src/lib/workers/bundler/plugins/commonjs.js rename to packages/repl/src/lib/workers/bundler/plugins/commonjs.ts index 9e0a92dbd..ecaeea032 100644 --- a/packages/repl/src/lib/workers/bundler/plugins/commonjs.js +++ b/packages/repl/src/lib/workers/bundler/plugins/commonjs.ts @@ -1,4 +1,6 @@ +import type { Plugin } from '@rollup/browser'; import { parse } from 'acorn'; +import type { Node } from 'estree'; import { walk } from 'zimmerframe'; const require = `function require(id) { @@ -6,8 +8,7 @@ const require = `function require(id) { throw new Error(\`Cannot require modules dynamically (\${id})\`); }`; -/** @type {import('@rollup/browser').Plugin} */ -export default { +const plugin: Plugin = { name: 'commonjs', transform: (code, id) => { @@ -21,7 +22,7 @@ export default { /** @type {string[]} */ const requires = []; - walk(/** @type {import('estree').Node} */ (ast), null, { + walk(ast as Node, null, { CallExpression: (node) => { if (node.callee.type === 'Identifier' && node.callee.name === 'require') { if (node.arguments.length !== 1) return; @@ -56,3 +57,5 @@ export default { } } }; + +export default plugin; diff --git a/packages/repl/src/lib/workers/bundler/plugins/glsl.js b/packages/repl/src/lib/workers/bundler/plugins/glsl.ts similarity index 63% rename from packages/repl/src/lib/workers/bundler/plugins/glsl.js rename to packages/repl/src/lib/workers/bundler/plugins/glsl.ts index 51e7e062a..11dcfd998 100644 --- a/packages/repl/src/lib/workers/bundler/plugins/glsl.js +++ b/packages/repl/src/lib/workers/bundler/plugins/glsl.ts @@ -1,5 +1,6 @@ -/** @type {import('@rollup/browser').Plugin} */ -export default { +import type { Plugin } from '@rollup/browser'; + +const plugin: Plugin = { name: 'glsl', transform: (code, id) => { if (!id.endsWith('.glsl')) return; @@ -10,3 +11,5 @@ export default { }; } }; + +export default plugin; diff --git a/packages/repl/src/lib/workers/bundler/plugins/json.js b/packages/repl/src/lib/workers/bundler/plugins/json.ts similarity index 61% rename from packages/repl/src/lib/workers/bundler/plugins/json.js rename to packages/repl/src/lib/workers/bundler/plugins/json.ts index 2f79b289e..3be12c535 100644 --- a/packages/repl/src/lib/workers/bundler/plugins/json.js +++ b/packages/repl/src/lib/workers/bundler/plugins/json.ts @@ -1,5 +1,6 @@ -/** @type {import('@rollup/browser').Plugin} */ -export default { +import type { Plugin } from '@rollup/browser'; + +const plugin: Plugin = { name: 'json', transform: (code, id) => { if (!id.endsWith('.json')) return; @@ -10,3 +11,5 @@ export default { }; } }; + +export default plugin; diff --git a/packages/repl/src/lib/workers/bundler/plugins/loop-protect.js b/packages/repl/src/lib/workers/bundler/plugins/loop-protect.ts similarity index 57% rename from packages/repl/src/lib/workers/bundler/plugins/loop-protect.js rename to packages/repl/src/lib/workers/bundler/plugins/loop-protect.ts index 9cb4a8e25..6c978543c 100644 --- a/packages/repl/src/lib/workers/bundler/plugins/loop-protect.js +++ b/packages/repl/src/lib/workers/bundler/plugins/loop-protect.ts @@ -1,18 +1,25 @@ +import type { Plugin } from '@rollup/browser'; import { parse } from 'acorn'; import { print } from 'esrap'; -import { walk } from 'zimmerframe'; +import type { + ArrowFunctionExpression, + BlockStatement, + DoWhileStatement, + ForStatement, + FunctionDeclaration, + FunctionExpression, + Node, + Statement, + WhileStatement +} from 'estree'; +import { walk, type Context } from 'zimmerframe'; const TIMEOUT = 100; const regex = /\b(for|while)\b/; -/** - * - * @param {string} code - * @returns {import('estree').Statement} - */ -function parse_statement(code) { - return /** @type {import('estree').Statement} */ (parse(code, { ecmaVersion: 'latest' }).body[0]); +function parse_statement(code: string): Statement { + return parse(code, { ecmaVersion: 'latest' }).body[0] as Statement; } const declaration = parse_statement(` @@ -25,12 +32,9 @@ const check = parse_statement(` } `); -/** - * - * @param {import('estree').Node[]} path - * @returns {null | import('estree').FunctionExpression | import('estree').FunctionDeclaration | import('estree').ArrowFunctionExpression} - */ -export function get_current_function(path) { +export function get_current_function( + path: Node[] +): null | FunctionExpression | FunctionDeclaration | ArrowFunctionExpression { for (let i = path.length - 1; i >= 0; i--) { const node = path[i]; if ( @@ -44,27 +48,23 @@ export function get_current_function(path) { return null; } -/** - * @template {import('estree').DoWhileStatement | import('estree').ForStatement | import('estree').WhileStatement} Statement - * @param {Statement} node - * @param {import('zimmerframe').Context} context - * @returns {import('estree').Node | void} - */ -function loop_protect(node, context) { +function loop_protect( + node: Statement, + context: Context +): Node | void { const current_function = get_current_function(context.path); if (current_function === null || (!current_function.async && !current_function.generator)) { - const body = /** @type {import('estree').Statement} */ (context.visit(node.body)); + const body = context.visit(node.body) as import('estree').Statement; const statements = body.type === 'BlockStatement' ? [...body.body] : [body]; - /** @type {import('estree').BlockStatement} */ - const replacement = { + const replacement: BlockStatement = { type: 'BlockStatement', body: [ declaration, { - .../** @type {Statement} */ (context.next() ?? node), + ...((context.next() ?? node) as Statement), body: { type: 'BlockStatement', body: [...statements, check] @@ -79,8 +79,7 @@ function loop_protect(node, context) { context.next(); } -/** @type {import('@rollup/browser').Plugin} */ -export default { +const plugin: Plugin = { name: 'loop-protect', transform: (code, id) => { // only applies to local files, not imports @@ -97,7 +96,7 @@ export default { sourceType: 'module' }); - const transformed = walk(/** @type {import('estree').Node} */ (ast), null, { + const transformed = walk(ast as Node, null, { WhileStatement: loop_protect, DoWhileStatement: loop_protect, ForStatement: loop_protect @@ -109,3 +108,5 @@ export default { return print(transformed); } }; + +export default plugin; diff --git a/packages/repl/src/lib/workers/bundler/plugins/replace.js b/packages/repl/src/lib/workers/bundler/plugins/replace.ts similarity index 57% rename from packages/repl/src/lib/workers/bundler/plugins/replace.js rename to packages/repl/src/lib/workers/bundler/plugins/replace.ts index 6ccdeffed..e81d9510d 100644 --- a/packages/repl/src/lib/workers/bundler/plugins/replace.js +++ b/packages/repl/src/lib/workers/bundler/plugins/replace.ts @@ -1,10 +1,10 @@ -/** @param {string} str */ -function escape(str) { +import type { Plugin } from '@rollup/browser'; + +function escape(str: string) { return str.replace(/[-[\]/{}()*+?.\\^$|]/g, '\\$&'); } -/** @param {unknown} functionOrValue */ -function ensureFunction(functionOrValue) { +function ensureFunction(functionOrValue: unknown) { if (typeof functionOrValue === 'function') { return functionOrValue; } @@ -13,30 +13,18 @@ function ensureFunction(functionOrValue) { }; } -/** - * @param {string} a - * @param {string} b - */ -function longest(a, b) { +function longest(a: string, b: string) { return b.length - a.length; } -/** @param {Record} object */ -function mapToFunctions(object) { - return Object.keys(object).reduce( - /** @param {Record} functions */ function (functions, key) { - functions[key] = ensureFunction(object[key]); - return functions; - }, - {} - ); +function mapToFunctions(object: Record) { + return Object.keys(object).reduce(function (functions: Record, key) { + functions[key] = ensureFunction(object[key]); + return functions; + }, {}); } -/** - * @param {Record} options - * @returns {import('@rollup/browser').Plugin} - */ -function replace(options) { +function replace(options: Record): Plugin { const functionValues = mapToFunctions(options); const keys = Object.keys(functionValues).sort(longest).map(escape); diff --git a/packages/repl/src/lib/workers/compiler/index.js b/packages/repl/src/lib/workers/compiler/index.ts similarity index 61% rename from packages/repl/src/lib/workers/compiler/index.js rename to packages/repl/src/lib/workers/compiler/index.ts index af1839724..82068818e 100644 --- a/packages/repl/src/lib/workers/compiler/index.js +++ b/packages/repl/src/lib/workers/compiler/index.ts @@ -1,63 +1,55 @@ -/// +import type { CompilerCommand, CompilerInput, CompilerOutput, MigrateInput } from '../workers'; + self.window = self; //TODO: still need?: egregious hack to get magic-string to work in a worker -/** @type {(arg?: never) => void} */ -let fulfil_ready; +let fulfil_ready: (arg?: never) => void; const ready = new Promise((f) => { fulfil_ready = f; }); -self.addEventListener( - 'message', - /** @param {MessageEvent} event */ - async (event) => { - switch (event.data.type) { - case 'init': - const { svelte_url } = event.data; +self.addEventListener('message', async (event: MessageEvent) => { + switch (event.data.type) { + case 'init': + const { svelte_url } = event.data; - const { version } = await fetch(`${svelte_url}/package.json`) - .then((r) => r.json()) - .catch(() => ({ version: 'experimental' })); + const { version } = await fetch(`${svelte_url}/package.json`) + .then((r) => r.json()) + .catch(() => ({ version: 'experimental' })); - const compiler = await fetch(`${svelte_url}/compiler/index.js`).then((r) => r.text()); - (0, eval)(compiler + '\n//# sourceURL=compiler/index.js@' + version); + const compiler = await fetch(`${svelte_url}/compiler/index.js`).then((r) => r.text()); + (0, eval)(compiler + '\n//# sourceURL=compiler/index.js@' + version); - fulfil_ready(); - break; + fulfil_ready(); + break; - case 'compile': - await ready; + case 'compile': + await ready; - postMessage({ - id: event.data.id, - result: compile(event.data.payload) - }); + postMessage({ + id: event.data.id, + result: compile(event.data.payload) + }); - break; + break; - case 'migrate': - await ready; + case 'migrate': + await ready; - postMessage({ - id: event.data.id, - result: migrate(event.data.payload) - }); + postMessage({ + id: event.data.id, + result: migrate(event.data.payload) + }); - break; - } + break; } -); +}); const common_options = { dev: false, css: false }; -/** - * @param {import("../workers").CompilerInput} param0 - * @returns {import("../workers").CompilerOutput} - */ -function compile({ source, options, return_ast }) { +function compile({ source, options, return_ast }: CompilerInput): CompilerOutput { try { const css = `/* Select a component to see compiled CSS */`; @@ -120,8 +112,7 @@ function compile({ source, options, return_ast }) { } } -/** @param {import("../workers").MigrateInput} param0 */ -function migrate({ source }) { +function migrate({ source }: MigrateInput) { try { const result = svelte.migrate(source); diff --git a/packages/repl/src/lib/workers/jsconfig.json b/packages/repl/src/lib/workers/jsconfig.json deleted file mode 100644 index 60351b754..000000000 --- a/packages/repl/src/lib/workers/jsconfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "include": ["./**/*"], - "compilerOptions": { - "paths": { - "svelte": ["../../../static/svelte/main"], - "svelte/*": ["../../../static/svelte/*"] - } - } -} diff --git a/packages/repl/src/lib/workers/patch_window.js b/packages/repl/src/lib/workers/patch_window.ts similarity index 100% rename from packages/repl/src/lib/workers/patch_window.js rename to packages/repl/src/lib/workers/patch_window.ts diff --git a/packages/repl/src/routes/+page.svelte b/packages/repl/src/routes/+page.svelte index 79b280104..a2df2bde0 100644 --- a/packages/repl/src/routes/+page.svelte +++ b/packages/repl/src/routes/+page.svelte @@ -1,10 +1,9 @@ -