Skip to content

Commit

Permalink
breaking: change $inspect API (#9838)
Browse files Browse the repository at this point in the history
* breaking: change `$inspect` API

`$inspect` now takes 1-n arguments, and inspections modification happens through `.with(..)`
closes #9737

* lint
  • Loading branch information
dummdidumm authored Dec 7, 2023
1 parent 26c6d6f commit df5105e
Show file tree
Hide file tree
Showing 18 changed files with 155 additions and 62 deletions.
5 changes: 5 additions & 0 deletions .changeset/wise-donkeys-marry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'svelte': patch
---

breaking: change `$inspect` API
2 changes: 1 addition & 1 deletion packages/svelte/src/compiler/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ const runes = {
'invalid-derived-binding': () => `Invalid binding to derived state`,
/**
* @param {string} rune
* @param {number[]} args
* @param {Array<number | string>} args
*/
'invalid-rune-args-length': (rune, args) =>
`${rune} can only be called with ${list(args, 'or')} ${
Expand Down
10 changes: 8 additions & 2 deletions packages/svelte/src/compiler/phases/2-analyze/validation.js
Original file line number Diff line number Diff line change
Expand Up @@ -711,8 +711,14 @@ function validate_call_expression(node, scope, path) {
}

if (rune === '$inspect') {
if (node.arguments.length < 1 || node.arguments.length > 2) {
error(node, 'invalid-rune-args-length', rune, [1, 2]);
if (node.arguments.length < 1) {
error(node, 'invalid-rune-args-length', rune, [1, 'more']);
}
}

if (rune === '$inspect().with') {
if (node.arguments.length !== 1) {
error(node, 'invalid-rune-args-length', rune, [1]);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { get_rune } from '../../../scope.js';
import { is_hoistable_function } from '../../utils.js';
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 { create_state_declarators, get_prop_source, should_proxy } from '../utils.js';
Expand Down Expand Up @@ -301,33 +301,24 @@ export const javascript_visitors_runes = {

context.next();
},
CallExpression(node, { state, next, visit }) {
const rune = get_rune(node, state.scope);
CallExpression(node, context) {
const rune = get_rune(node, context.state.scope);

if (rune === '$effect.active') {
return b.call('$.effect_active');
}

if (rune === '$effect.root') {
const args = /** @type {import('estree').Expression[]} */ (
node.arguments.map((arg) => visit(arg))
node.arguments.map((arg) => context.visit(arg))
);
return b.call('$.user_root_effect', ...args);
}

if (rune === '$inspect') {
if (state.options.dev) {
const arg = /** @type {import('estree').Expression} */ (visit(node.arguments[0]));
const fn =
node.arguments[1] &&
/** @type {import('estree').Expression} */ (visit(node.arguments[1]));

return b.call('$.inspect', b.thunk(arg), fn);
}

return b.unary('void', b.literal(0));
if (rune === '$inspect' || rune === '$inspect().with') {
return transform_inspect_rune(node, context);
}

next();
context.next();
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ import {
clean_nodes,
determine_element_namespace,
escape_html,
infer_namespace
infer_namespace,
transform_inspect_rune
} from '../utils.js';
import { create_attribute, is_custom_element_node, is_element_node } from '../../nodes.js';
import { error } from '../../../errors.js';
Expand Down Expand Up @@ -630,26 +631,18 @@ const javascript_visitors_runes = {
}
context.next();
},
CallExpression(node, { state, next, visit }) {
const rune = get_rune(node, state.scope);
CallExpression(node, context) {
const rune = get_rune(node, context.state.scope);

if (rune === '$effect.active') {
return b.literal(false);
}

if (rune === '$inspect') {
if (state.options.dev) {
const args = /** @type {import('estree').Expression[]} */ (
node.arguments.map((arg) => visit(arg))
);

return b.call('console.log', ...args);
}

return b.unary('void', b.literal(0));
if (rune === '$inspect' || rune === '$inspect().with') {
return transform_inspect_rune(node, context);
}

next();
context.next();
}
};

Expand Down
32 changes: 32 additions & 0 deletions packages/svelte/src/compiler/phases/3-transform/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -271,3 +271,35 @@ export function determine_element_namespace(node, namespace, path) {

return namespace;
}

/**
* @template {import('./types.js').TransformState} T
* @param {import('estree').CallExpression} node
* @param {import('zimmerframe').Context<any, T>} context
*/
export function transform_inspect_rune(node, context) {
const { state, visit } = context;
const as_fn = state.options.generate === 'client';

if (!state.options.dev) return b.unary('void', b.literal(0));

if (node.callee.type === 'MemberExpression') {
const raw_inspect_args = /** @type {import('estree').CallExpression} */ (node.callee.object)
.arguments;
const inspect_args =
/** @type {Array<import('estree').Expression>} */
(raw_inspect_args.map((arg) => visit(arg)));
const with_arg = /** @type {import('estree').Expression} */ (visit(node.arguments[0]));

return b.call(
'$.inspect',
as_fn ? b.thunk(b.array(inspect_args)) : b.array(inspect_args),
with_arg
);
} else {
const arg = node.arguments.map(
(arg) => /** @type {import('estree').Expression} */ (visit(arg))
);
return b.call('$.inspect', as_fn ? b.thunk(b.array(arg)) : b.array(arg));
}
}
3 changes: 2 additions & 1 deletion packages/svelte/src/compiler/phases/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@ export const Runes = /** @type {const} */ ([
'$effect.pre',
'$effect.active',
'$effect.root',
'$inspect'
'$inspect',
'$inspect().with'
]);

/**
Expand Down
5 changes: 5 additions & 0 deletions packages/svelte/src/compiler/phases/scope.js
Original file line number Diff line number Diff line change
Expand Up @@ -699,6 +699,11 @@ export function get_rune(node, scope) {
n = n.object;
}

if (n.type === 'CallExpression' && n.callee.type === 'Identifier') {
joined = '()' + joined;
n = n.callee;
}

if (n.type !== 'Identifier') return null;

joined = n.name + joined;
Expand Down
21 changes: 15 additions & 6 deletions packages/svelte/src/internal/client/runtime.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
PROPS_IS_UPDATED
} from '../../constants.js';
import { readonly } from './proxy/readonly.js';
import { proxy } from './proxy/proxy.js';
import { proxy, unstate } from './proxy/proxy.js';

export const SOURCE = 1;
export const DERIVED = 1 << 1;
Expand Down Expand Up @@ -1775,19 +1775,28 @@ function deep_read(value, visited = new Set()) {
}
}

// TODO remove in a few versions, before 5.0 at the latest
let warned_inspect_changed = false;

/**
* @param {() => any} get_value
* @param {Function} inspect
* @returns {void}
* @param {() => any[]} get_value
* @param {Function} [inspect]
*/
// eslint-disable-next-line no-console
export function inspect(get_value, inspect = console.log) {
let initial = true;

pre_effect(() => {
const fn = () => {
const value = get_value();
inspect(value, initial ? 'init' : 'update');
const value = get_value().map(unstate);
if (value.length === 2 && typeof value[1] === 'function' && !warned_inspect_changed) {
// eslint-disable-next-line no-console
console.warn(
'$inspect() API has changed. See https://svelte-5-preview.vercel.app/docs/runes#$inspect for more information.'
);
warned_inspect_changed = true;
}
inspect(initial ? 'init' : 'update', ...value);
};

inspect_fn = fn;
Expand Down
9 changes: 9 additions & 0 deletions packages/svelte/src/internal/server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -552,3 +552,12 @@ export function loop_guard(timeout) {
}
};
}

/**
* @param {any[]} args
* @param {Function} [inspect]
*/
// eslint-disable-next-line no-console
export function inspect(args, inspect = console.log) {
inspect('init', ...args);
}
20 changes: 10 additions & 10 deletions packages/svelte/src/main/ambient.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,23 +132,23 @@ declare namespace $effect {
declare function $props<T>(): T;

/**
* Inspects a value whenever it, or the properties it contains, change. Example:
* Inspects one or more values whenever they, or the properties they contain, change. Example:
*
* ```ts
* $inspect({ someValue, someOtherValue })
* $inspect(someValue, someOtherValue)
* ```
*
* If a second argument is provided, it will be called with the value and the event type
* (`'init'` or `'update'`), otherwise the value will be logged to the console.
* `$inspect` returns a `with` function, which you can invoke with a callback function that
* will be called with the value and the event type (`'init'` or `'update'`) on every change.
* By default, the values will be logged to the console.
*
* ```ts
* $inspect(x, console.trace);
* $inspect(y, (y) => { debugger; });
* $inspect(x).with(console.trace);
* $inspect(x, y).with(() => { debugger; });
* ```
*
* https://svelte-5-preview.vercel.app/docs/runes#$inspect
*/
declare function $inspect<T>(
value: T,
callback?: (value: T, type: 'init' | 'update') => void
): void;
declare function $inspect<T extends any[]>(
...values: T
): { with: (type: 'init' | 'update', ...values: T) => void };
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,6 @@ export default test({
button?.click();
await Promise.resolve();

assert.deepEqual(log, ['X', 'init', 'XX', 'update', 'XXX', 'update']);
assert.deepEqual(log, ['init', 'X', 'update', 'XX', 'update', 'XXX']);
}
});
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
let x = $state('x');
let y = $derived(x.toUpperCase());
$inspect(y, push);
$inspect(y).with(push);
</script>

<button on:click={() => x += 'x'}>{x}</button>
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { test } from '../../test';

/**
* @type {any[]}
*/
let log;
/**
* @type {typeof console.log}}
*/
let original_log;

export default test({
compileOptions: {
dev: true
},
before_test() {
log = [];
original_log = console.log;
console.log = (...v) => {
log.push(...v);
};
},
after_test() {
console.log = original_log;
},
async test({ assert, target }) {
const [b1, b2] = target.querySelectorAll('button');
b1.click();
b2.click();
await Promise.resolve();

assert.deepEqual(log, ['init', 0, 0, 'update', 1, 0, 'update', 1, 1]);
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<script>
let x = $state(0);
let y = $state(0);
$inspect(x, y);
</script>

<button on:click={() => x++}>{x}</button>
<button on:click={() => y++}>{y}</button>
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
let x = $state(0);
let y = $state(0);
$inspect(x, (x, type) => {
$inspect(x).with((type, x) => {
if (type === 'update') console.log(new Error(), x);
});
</script>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ export default test({
after_test() {
console.log = original_log;
},
async test({ assert, target, component }) {
async test({ assert, target }) {
const [b1, b2] = target.querySelectorAll('button');
b1.click();
b2.click();
await Promise.resolve();

assert.deepEqual(log, [0, 'init', 1, 'update']);
assert.deepEqual(log, ['init', 0, 'update', 1]);
}
});
Loading

0 comments on commit df5105e

Please sign in to comment.