fix: handle ts expressions when dealing with runes (#9681)

* fix: handle ts expressions when dealing with runes

related to #9639

* docs, more tests

* simplify

---------

Co-authored-by: Rich Harris <rich.harris@vercel.com>
pull/9692/head
Simon H 7 months ago committed by GitHub
parent a31b2e1b8e
commit aabab263ee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: handle ts expressions when dealing with runes

@ -7,7 +7,8 @@ import {
extract_paths,
is_event_attribute,
is_text_attribute,
object
object,
unwrap_ts_expression
} from '../../utils/ast.js';
import * as b from '../../utils/builders.js';
import { ReservedKeywords, Runes, SVGElements } from '../constants.js';
@ -660,10 +661,11 @@ const runes_scope_js_tweaker = {
/** @type {import('./types').Visitors} */
const runes_scope_tweaker = {
VariableDeclarator(node, { state }) {
if (node.init?.type !== 'CallExpression') return;
if (get_rune(node.init, state.scope) === null) return;
const init = unwrap_ts_expression(node.init);
if (!init || init.type !== 'CallExpression') return;
if (get_rune(init, state.scope) === null) return;
const callee = node.init.callee;
const callee = init.callee;
if (callee.type !== 'Identifier') return;
const name = callee.name;

@ -1,5 +1,10 @@
import { error } from '../../errors.js';
import { extract_identifiers, is_text_attribute } from '../../utils/ast.js';
import {
extract_identifiers,
get_parent,
is_text_attribute,
unwrap_ts_expression
} from '../../utils/ast.js';
import { warn } from '../../warnings.js';
import fuzzymatch from '../1-parse/utils/fuzzymatch.js';
import { binding_properties } from '../bindings.js';
@ -491,7 +496,7 @@ function validate_call_expression(node, scope, path) {
const rune = get_rune(node, scope);
if (rune === null) return;
const parent = /** @type {import('#compiler').SvelteNode} */ (path.at(-1));
const parent = /** @type {import('#compiler').SvelteNode} */ (get_parent(path, -1));
if (rune === '$props') {
if (parent.type === 'VariableDeclarator') return;
@ -703,7 +708,7 @@ export const validation_runes = merge(validation, a11y_validators, {
next({ ...state });
},
VariableDeclarator(node, { state }) {
const init = node.init;
const init = unwrap_ts_expression(node.init);
const rune = get_rune(init, state.scope);
if (rune === null) return;

@ -3,6 +3,7 @@ import { is_hoistable_function } from '../../utils.js';
import * as b from '../../../../utils/builders.js';
import * as assert from '../../../../utils/assert.js';
import { create_state_declarators, get_props_method } from '../utils.js';
import { unwrap_ts_expression } from '../../../../utils/ast.js';
/** @type {import('../types.js').ComponentVisitors} */
export const javascript_visitors_runes = {
@ -133,7 +134,7 @@ export const javascript_visitors_runes = {
const declarations = [];
for (const declarator of node.declarations) {
const init = declarator.init;
const init = unwrap_ts_expression(declarator.init);
const rune = get_rune(init, state.scope);
if (!rune || rune === '$effect.active' || rune === '$effect.root') {
if (init != null && is_hoistable_function(init)) {
@ -208,7 +209,8 @@ export const javascript_visitors_runes = {
// TODO
continue;
}
const args = /** @type {import('estree').CallExpression} */ (declarator.init).arguments;
const args = /** @type {import('estree').CallExpression} */ (init).arguments;
const value =
args.length === 0
? b.id('undefined')

@ -1,6 +1,11 @@
import { walk } from 'zimmerframe';
import { set_scope, get_rune } from '../../scope.js';
import { extract_identifiers, extract_paths, is_event_attribute } from '../../../utils/ast.js';
import {
extract_identifiers,
extract_paths,
is_event_attribute,
unwrap_ts_expression
} from '../../../utils/ast.js';
import * as b from '../../../utils/builders.js';
import is_reference from 'is-reference';
import {
@ -568,7 +573,8 @@ const javascript_visitors_runes = {
const declarations = [];
for (const declarator of node.declarations) {
const rune = get_rune(declarator.init, state.scope);
const init = unwrap_ts_expression(declarator.init);
const rune = get_rune(init, state.scope);
if (!rune || rune === '$effect.active') {
declarations.push(/** @type {import('estree').VariableDeclarator} */ (visit(declarator)));
continue;
@ -579,7 +585,7 @@ const javascript_visitors_runes = {
continue;
}
const args = /** @type {import('estree').CallExpression} */ (declarator.init).arguments;
const args = /** @type {import('estree').CallExpression} */ (init).arguments;
const value =
args.length === 0
? b.id('undefined')

@ -265,3 +265,42 @@ function _extract_paths(assignments = [], param, expression, update_expression)
return assignments;
}
/**
* The Acorn TS plugin defines `foo!` as a `TSNonNullExpression` node, and
* `foo as Bar` as a `TSAsExpression` node. This function unwraps those.
*
* @template {import('#compiler').SvelteNode | undefined | null} T
* @param {T} node
* @returns {T}
*/
export function unwrap_ts_expression(node) {
if (!node) {
return node;
}
// @ts-expect-error these types don't exist on the base estree types
if (node.type === 'TSNonNullExpression' || node.type === 'TSAsExpression') {
// @ts-expect-error
return node.expression;
}
return node;
}
/**
* Like `path.at(x)`, but skips over `TSNonNullExpression` and `TSAsExpression` nodes and eases assertions a bit
* by removing the `| undefined` from the resulting type.
*
* @template {import('#compiler').SvelteNode} T
* @param {T[]} path
* @param {number} at
*/
export function get_parent(path, at) {
let node = path.at(at);
// @ts-expect-error
if (node.type === 'TSNonNullExpression' || node.type === 'TSAsExpression') {
return /** @type {T} */ (path.at(at < 0 ? at - 1 : at + 1));
}
return /** @type {T} */ (node);
}

@ -0,0 +1,5 @@
import { test } from '../../test';
export default test({
html: '1 2'
});

@ -0,0 +1,6 @@
<script lang="ts">
let count = $state(1) as number;
let double = $derived(count as number * 2) as number;
</script>
{count as number} {double as number}

@ -0,0 +1,5 @@
import { test } from '../../test';
export default test({
html: '1 2'
});

@ -0,0 +1,6 @@
<script lang="ts">
let count = $state(1)!;
let double = $derived(count! * 2)!;
</script>
{count!} {double!}
Loading…
Cancel
Save