diff --git a/.changeset/sixty-items-crash.md b/.changeset/sixty-items-crash.md new file mode 100644 index 0000000000..76d830ea66 --- /dev/null +++ b/.changeset/sixty-items-crash.md @@ -0,0 +1,5 @@ +--- +"svelte": patch +--- + +fix: bindings with typescript assertions diff --git a/packages/svelte/src/compiler/index.js b/packages/svelte/src/compiler/index.js index 3e83a17798..09ab56d788 100644 --- a/packages/svelte/src/compiler/index.js +++ b/packages/svelte/src/compiler/index.js @@ -1,12 +1,13 @@ -import { parse as _parse } from './phases/1-parse/index.js'; +import { getLocator } from 'locate-character'; +import { walk } from 'zimmerframe'; +import { CompileError } from './errors.js'; +import { convert } from './legacy.js'; import { parse as parse_acorn } from './phases/1-parse/acorn.js'; +import { parse as _parse } from './phases/1-parse/index.js'; +import { remove_typescript_nodes } from './phases/1-parse/remove_typescript_nodes.js'; import { analyze_component, analyze_module } from './phases/2-analyze/index.js'; import { transform_component, transform_module } from './phases/3-transform/index.js'; -import { getLocator } from 'locate-character'; -import { walk } from 'zimmerframe'; import { validate_component_options, validate_module_options } from './validate-options.js'; -import { convert } from './legacy.js'; -import { CompileError } from './errors.js'; export { default as preprocess } from './preprocess/index.js'; /** @@ -20,14 +21,24 @@ export { default as preprocess } from './preprocess/index.js'; export function compile(source, options) { try { const validated = validate_component_options(options, ''); - const parsed = _parse(source); + let parsed = _parse(source); const combined_options = /** @type {import('#compiler').ValidatedCompileOptions} */ ({ ...validated, ...parsed.options }); + if (parsed.metadata.ts) { + parsed = { + ...parsed, + fragment: parsed.fragment && remove_typescript_nodes(parsed.fragment), + instance: parsed.instance && remove_typescript_nodes(parsed.instance), + module: parsed.module && remove_typescript_nodes(parsed.module) + }; + } + const analysis = analyze_component(parsed, combined_options); + const result = transform_component(analysis, source, combined_options); return result; } catch (e) { diff --git a/packages/svelte/src/compiler/phases/1-parse/index.js b/packages/svelte/src/compiler/phases/1-parse/index.js index e99e91d052..acf96678ff 100644 --- a/packages/svelte/src/compiler/phases/1-parse/index.js +++ b/packages/svelte/src/compiler/phases/1-parse/index.js @@ -67,7 +67,10 @@ export class Parser { end: null, type: 'Root', fragment: create_fragment(), - options: null + options: null, + metadata: { + ts: this.ts + } }; this.stack.push(this.root); diff --git a/packages/svelte/src/compiler/phases/3-transform/typescript.js b/packages/svelte/src/compiler/phases/1-parse/remove_typescript_nodes.js similarity index 83% rename from packages/svelte/src/compiler/phases/3-transform/typescript.js rename to packages/svelte/src/compiler/phases/1-parse/remove_typescript_nodes.js index e34d75691e..70e8576dcf 100644 --- a/packages/svelte/src/compiler/phases/3-transform/typescript.js +++ b/packages/svelte/src/compiler/phases/1-parse/remove_typescript_nodes.js @@ -1,7 +1,8 @@ +import { walk } from 'zimmerframe'; import * as b from '../../utils/builders.js'; -/** @type {import('zimmerframe').Visitors} */ -export const remove_types = { +/** @type {import('zimmerframe').Visitors} */ +const visitors = { ImportDeclaration(node) { if (node.importKind === 'type') return b.empty; @@ -54,3 +55,12 @@ export const remove_types = { return b.empty; } }; + +/** + * @template T + * @param {T} ast + * @returns {T} + */ +export function remove_typescript_nodes(ast) { + return walk(ast, null, visitors); +} diff --git a/packages/svelte/src/compiler/phases/2-analyze/index.js b/packages/svelte/src/compiler/phases/2-analyze/index.js index 1be19a399d..28e7e9a655 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/index.js +++ b/packages/svelte/src/compiler/phases/2-analyze/index.js @@ -7,8 +7,7 @@ import { extract_paths, is_event_attribute, is_text_attribute, - object, - unwrap_ts_expression + object } from '../../utils/ast.js'; import * as b from '../../utils/builders.js'; import { ReservedKeywords, Runes, SVGElements } from '../constants.js'; @@ -706,7 +705,7 @@ const runes_scope_tweaker = { } }, VariableDeclarator(node, { state }) { - const init = unwrap_ts_expression(node.init); + const init = node.init; if (!init || init.type !== 'CallExpression') return; const rune = get_rune(init, state.scope); if (rune === null) return; diff --git a/packages/svelte/src/compiler/phases/2-analyze/validation.js b/packages/svelte/src/compiler/phases/2-analyze/validation.js index 44068b2feb..8863b617b3 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/validation.js +++ b/packages/svelte/src/compiler/phases/2-analyze/validation.js @@ -1,10 +1,5 @@ import { error } from '../../errors.js'; -import { - extract_identifiers, - get_parent, - is_text_attribute, - unwrap_ts_expression -} from '../../utils/ast.js'; +import { extract_identifiers, get_parent, is_text_attribute } from '../../utils/ast.js'; import { warn } from '../../warnings.js'; import fuzzymatch from '../1-parse/utils/fuzzymatch.js'; import { disallowed_parapgraph_contents, interactive_elements } from '../1-parse/utils/html.js'; @@ -338,11 +333,11 @@ const validation = { BindDirective(node, context) { validate_no_const_assignment(node, node.expression, context.state.scope, true); - const assignee = unwrap_ts_expression(node.expression); + const assignee = node.expression; let left = assignee; while (left.type === 'MemberExpression') { - left = unwrap_ts_expression(/** @type {import('estree').MemberExpression} */ (left.object)); + left = /** @type {import('estree').MemberExpression} */ (left.object); } if (left.type !== 'Identifier') { @@ -950,7 +945,7 @@ export const validation_runes = merge(validation, a11y_validators, { next({ ...state }); }, VariableDeclarator(node, { state, path }) { - const init = unwrap_ts_expression(node.init); + const init = node.init; const rune = get_rune(init, state.scope); if (rune === null) return; diff --git a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js index 1dfafd64b4..124eee0a52 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js @@ -8,7 +8,6 @@ import { javascript_visitors } from './visitors/javascript.js'; import { javascript_visitors_runes } from './visitors/javascript-runes.js'; import { javascript_visitors_legacy } from './visitors/javascript-legacy.js'; import { is_state_source, serialize_get_binding } from './utils.js'; -import { remove_types } from '../typescript.js'; /** * This function ensures visitor sets don't accidentally clobber each other @@ -102,7 +101,6 @@ export function client_component(source, analysis, options) { state, combine_visitors( set_scope(analysis.module.scopes), - remove_types, global_visitors, // @ts-expect-error TODO javascript_visitors, @@ -118,21 +116,18 @@ export function client_component(source, analysis, options) { instance_state, combine_visitors( set_scope(analysis.instance.scopes), - { ...remove_types, ImportDeclaration: undefined, ExportNamedDeclaration: undefined }, global_visitors, // @ts-expect-error TODO javascript_visitors, analysis.runes ? javascript_visitors_runes : javascript_visitors_legacy, { - ImportDeclaration(node, context) { - // @ts-expect-error - state.hoisted.push(remove_types.ImportDeclaration(node, context)); + ImportDeclaration(node) { + state.hoisted.push(node); return b.empty; }, ExportNamedDeclaration(node, context) { if (node.declaration) { - // @ts-expect-error - return remove_types.ExportNamedDeclaration(context.visit(node.declaration), context); + return context.visit(node.declaration); } return b.empty; @@ -148,7 +143,6 @@ export function client_component(source, analysis, options) { { ...state, scope: analysis.instance.scope }, combine_visitors( set_scope(analysis.template.scopes), - remove_types, global_visitors, // @ts-expect-error TODO template_visitors diff --git a/packages/svelte/src/compiler/phases/3-transform/client/utils.js b/packages/svelte/src/compiler/phases/3-transform/client/utils.js index bd3a3fd441..ee2ed36544 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/utils.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/utils.js @@ -1,10 +1,5 @@ import * as b from '../../../utils/builders.js'; -import { - extract_paths, - is_simple_expression, - object, - unwrap_ts_expression -} from '../../../utils/ast.js'; +import { extract_paths, is_simple_expression, object } from '../../../utils/ast.js'; import { error } from '../../../errors.js'; import { PROPS_IS_LAZY_INITIAL, @@ -228,7 +223,7 @@ function is_expression_async(expression) { export function serialize_set_binding(node, context, fallback, options) { const { state, visit } = context; - const assignee = unwrap_ts_expression(node.left); + const assignee = node.left; if ( assignee.type === 'ArrayPattern' || assignee.type === 'ObjectPattern' || diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/javascript-runes.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/javascript-runes.js index bddac3f97a..e69cb42ae3 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/javascript-runes.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/javascript-runes.js @@ -3,7 +3,7 @@ import { is_hoistable_function, transform_inspect_rune } from '../../utils.js'; import * as b from '../../../../utils/builders.js'; import * as assert from '../../../../utils/assert.js'; import { get_prop_source, is_state_source, should_proxy_or_freeze } from '../utils.js'; -import { extract_paths, unwrap_ts_expression } from '../../../../utils/ast.js'; +import { extract_paths } from '../../../../utils/ast.js'; /** @type {import('../types.js').ComponentVisitors} */ export const javascript_visitors_runes = { @@ -174,7 +174,7 @@ export const javascript_visitors_runes = { const declarations = []; for (const declarator of node.declarations) { - const init = unwrap_ts_expression(declarator.init); + const init = declarator.init; const rune = get_rune(init, state.scope); if (!rune || rune === '$effect.active' || rune === '$effect.root' || rune === '$inspect') { if (init != null && is_hoistable_function(init)) { diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js index a3555aabb9..6e369a7ce5 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js @@ -3,8 +3,7 @@ import { extract_paths, is_event_attribute, is_text_attribute, - object, - unwrap_ts_expression + object } from '../../../../utils/ast.js'; import { binding_properties } from '../../../bindings.js'; import { @@ -2575,7 +2574,7 @@ export const template_visitors = { }, BindDirective(node, context) { const { state, path, visit } = context; - const expression = unwrap_ts_expression(node.expression); + const expression = node.expression; const getter = b.thunk(/** @type {import('estree').Expression} */ (visit(expression))); const assignment = b.assignment('=', expression, b.id('$$value')); const setter = b.arrow( diff --git a/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js b/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js index 1f0dcc9e1b..0d615d6693 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js @@ -1,11 +1,6 @@ import { walk } from 'zimmerframe'; import { set_scope, get_rune } from '../../scope.js'; -import { - extract_identifiers, - extract_paths, - is_event_attribute, - unwrap_ts_expression -} from '../../../utils/ast.js'; +import { extract_identifiers, extract_paths, is_event_attribute } from '../../../utils/ast.js'; import * as b from '../../../utils/builders.js'; import is_reference from 'is-reference'; import { @@ -24,7 +19,6 @@ import { create_attribute, is_custom_element_node, is_element_node } from '../.. import { error } from '../../../errors.js'; import { binding_properties } from '../../bindings.js'; import { regex_starts_with_newline, regex_whitespaces_strict } from '../../patterns.js'; -import { remove_types } from '../typescript.js'; import { DOMBooleanAttributes } from '../../../../constants.js'; import { sanitize_template_string } from '../../../utils/sanitize_template_string.js'; @@ -570,7 +564,7 @@ const javascript_visitors_runes = { const declarations = []; for (const declarator of node.declarations) { - const init = unwrap_ts_expression(declarator.init); + const init = declarator.init; const rune = get_rune(init, state.scope); if (!rune || rune === '$effect.active' || rune === '$inspect') { declarations.push(/** @type {import('estree').VariableDeclarator} */ (visit(declarator))); @@ -1963,34 +1957,36 @@ export function server_component(analysis, options) { }; const module = /** @type {import('estree').Program} */ ( - walk(/** @type {import('#compiler').SvelteNode} */ (analysis.module.ast), state, { - ...set_scope(analysis.module.scopes), - ...global_visitors, - ...remove_types, - ...javascript_visitors, - ...(analysis.runes ? javascript_visitors_runes : javascript_visitors_legacy) - }) + walk( + /** @type {import('#compiler').SvelteNode} */ (analysis.module.ast), + state, + // @ts-expect-error TODO: zimmerframe types + { + ...set_scope(analysis.module.scopes), + ...global_visitors, + ...javascript_visitors, + ...(analysis.runes ? javascript_visitors_runes : javascript_visitors_legacy) + } + ) ); const instance = /** @type {import('estree').Program} */ ( walk( /** @type {import('#compiler').SvelteNode} */ (analysis.instance.ast), { ...state, scope: analysis.instance.scope }, + // @ts-expect-error TODO: zimmerframe types { ...set_scope(analysis.instance.scopes), ...global_visitors, - ...{ ...remove_types, ImportDeclaration: undefined, ExportNamedDeclaration: undefined }, ...javascript_visitors, ...(analysis.runes ? javascript_visitors_runes : javascript_visitors_legacy), - ImportDeclaration(node, context) { - // @ts-expect-error - state.hoisted.push(remove_types.ImportDeclaration(node, context)); + ImportDeclaration(node) { + state.hoisted.push(node); return b.empty; }, ExportNamedDeclaration(node, context) { if (node.declaration) { - // @ts-expect-error - return remove_types.ExportNamedDeclaration(context.visit(node.declaration), context); + return context.visit(node.declaration); } return b.empty; @@ -2003,10 +1999,10 @@ export function server_component(analysis, options) { walk( /** @type {import('#compiler').SvelteNode} */ (analysis.template.ast), { ...state, scope: analysis.template.scope }, + // @ts-expect-error TODO: zimmerframe types { ...set_scope(analysis.template.scopes), ...global_visitors, - ...remove_types, ...template_visitors } ) diff --git a/packages/svelte/src/compiler/types/template.d.ts b/packages/svelte/src/compiler/types/template.d.ts index d9c6f51020..694b1231a5 100644 --- a/packages/svelte/src/compiler/types/template.d.ts +++ b/packages/svelte/src/compiler/types/template.d.ts @@ -58,6 +58,10 @@ export interface Root extends BaseNode { instance: Script | null; /** The parsed ` {count as number} {double as number}
+
diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts index e17e500faf..6414dc80b2 100644 --- a/packages/svelte/types/index.d.ts +++ b/packages/svelte/types/index.d.ts @@ -1087,6 +1087,10 @@ declare module 'svelte/compiler' { instance: Script | null; /** The parsed `