Skip to content

Commit 64d2a2e

Browse files
feat: allow ignoring runtime warnings (#12608)
* feat: allow ignoring binding_property_non_reactive * chore: add comments before `to_ignore` * chore: fix warnings regeneration * chore: include client warnings code in svelte ignore extract * feat: allow ignoring state_snapshot_uncloneable * chore: abstract ignore into function * feat: allow skipping of `hydration_attribute_changed` * feat: allow skip of `hydration_html_changed` * feat: allow skipping `ownership_invalid_binding` * chore: revert extracting codes and use hardcoded list * chore: update changeset * feat: allow skipping `ownership_invalid_mutation` * is_to_ignore -> is_ignored * make is_ignored type safe * tweak * tweak naming * tweak * remove extra args * comment is redundant, code contains enough information * remove more unwanted args * lint --------- Co-authored-by: Rich Harris <[email protected]>
1 parent 4b1b886 commit 64d2a2e

File tree

31 files changed

+419
-87
lines changed

31 files changed

+419
-87
lines changed

.changeset/large-emus-cough.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
feat: allow ignoring runtime warnings

packages/svelte/src/compiler/phases/3-transform/client/utils.js

Lines changed: 48 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {
1616
PROPS_IS_RUNES,
1717
PROPS_IS_UPDATED
1818
} from '../../../../constants.js';
19-
import { dev } from '../../../state.js';
19+
import { is_ignored, dev } from '../../../state.js';
2020

2121
/**
2222
* @template {ClientTransformState} State
@@ -282,6 +282,22 @@ export function serialize_set_binding(node, context, fallback, prefix, options)
282282
);
283283
}
284284

285+
/**
286+
* @param {any} serialized
287+
* @returns
288+
*/
289+
function maybe_skip_ownership_validation(serialized) {
290+
if (is_ignored(node, 'ownership_invalid_mutation')) {
291+
return b.call('$.skip_ownership_validation', b.thunk(serialized));
292+
}
293+
294+
return serialized;
295+
}
296+
297+
if (binding.kind === 'derived') {
298+
return maybe_skip_ownership_validation(fallback());
299+
}
300+
285301
const is_store = binding.kind === 'store_sub';
286302
const left_name = is_store ? left.name.slice(1) : left.name;
287303

@@ -382,45 +398,55 @@ export function serialize_set_binding(node, context, fallback, prefix, options)
382398
return /** @type {Expression} */ (visit(node));
383399
}
384400

385-
return b.call(
386-
'$.store_mutate',
387-
serialize_get_binding(b.id(left_name), state),
388-
b.assignment(node.operator, /** @type {Pattern}} */ (visit_node(node.left)), value),
389-
b.call('$.untrack', b.id('$' + left_name))
401+
return maybe_skip_ownership_validation(
402+
b.call(
403+
'$.store_mutate',
404+
serialize_get_binding(b.id(left_name), state),
405+
b.assignment(node.operator, /** @type {Pattern}} */ (visit_node(node.left)), value),
406+
b.call('$.untrack', b.id('$' + left_name))
407+
)
390408
);
391409
} else if (
392410
!state.analysis.runes ||
393411
// this condition can go away once legacy mode is gone; only necessary for interop with legacy parent bindings
394412
(binding.mutated && binding.kind === 'bindable_prop')
395413
) {
396414
if (binding.kind === 'bindable_prop') {
397-
return b.call(
398-
left,
399-
b.assignment(node.operator, /** @type {Pattern} */ (visit(node.left)), value),
400-
b.true
415+
return maybe_skip_ownership_validation(
416+
b.call(
417+
left,
418+
b.assignment(node.operator, /** @type {Pattern} */ (visit(node.left)), value),
419+
b.true
420+
)
401421
);
402422
} else {
403-
return b.call(
404-
'$.mutate',
405-
b.id(left_name),
406-
b.assignment(node.operator, /** @type {Pattern} */ (visit(node.left)), value)
423+
return maybe_skip_ownership_validation(
424+
b.call(
425+
'$.mutate',
426+
b.id(left_name),
427+
b.assignment(node.operator, /** @type {Pattern} */ (visit(node.left)), value)
428+
)
407429
);
408430
}
409431
} else if (
410432
node.right.type === 'Literal' &&
411433
prefix != null &&
412434
(node.operator === '+=' || node.operator === '-=')
413435
) {
414-
return b.update(
415-
node.operator === '+=' ? '++' : '--',
416-
/** @type {Expression} */ (visit(node.left)),
417-
prefix
436+
return maybe_skip_ownership_validation(
437+
b.update(
438+
node.operator === '+=' ? '++' : '--',
439+
/** @type {Expression} */ (visit(node.left)),
440+
prefix
441+
)
418442
);
419443
} else {
420-
return b.assignment(
421-
node.operator,
422-
/** @type {Pattern} */ (visit(node.left)),
423-
/** @type {Expression} */ (visit(node.right))
444+
return maybe_skip_ownership_validation(
445+
b.assignment(
446+
node.operator,
447+
/** @type {Pattern} */ (visit(node.left)),
448+
/** @type {Expression} */ (visit(node.right))
449+
)
424450
);
425451
}
426452
}

packages/svelte/src/compiler/phases/3-transform/client/visitors/global.js

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import is_reference from 'is-reference';
44
import { serialize_get_binding, serialize_set_binding } from '../utils.js';
55
import * as b from '../../../../utils/builders.js';
6+
import { is_ignored } from '../../../../state.js';
67

78
/** @type {Visitors} */
89
export const global_visitors = {
@@ -115,6 +116,15 @@ export const global_visitors = {
115116

116117
return b.call(fn, ...args);
117118
} else {
119+
/** @param {any} serialized */
120+
function maybe_skip_ownership_validation(serialized) {
121+
if (is_ignored(node, 'ownership_invalid_mutation')) {
122+
return b.call('$.skip_ownership_validation', b.thunk(serialized));
123+
}
124+
125+
return serialized;
126+
}
127+
118128
// turn it into an IIFEE assignment expression: i++ -> (() => { const $$value = i; i+=1; return $$value; })
119129
const assignment = b.assignment(
120130
node.operator === '++' ? '+=' : '-=',
@@ -130,9 +140,9 @@ export const global_visitors = {
130140
const value = /** @type {Expression} */ (visit(argument));
131141
if (serialized_assignment === assignment) {
132142
// No change to output -> nothing to transform -> we can keep the original update expression
133-
return next();
143+
return maybe_skip_ownership_validation(next());
134144
} else if (context.state.analysis.runes) {
135-
return serialized_assignment;
145+
return maybe_skip_ownership_validation(serialized_assignment);
136146
} else {
137147
/** @type {Statement[]} */
138148
let statements;
@@ -146,7 +156,7 @@ export const global_visitors = {
146156
b.return(b.id(tmp_id))
147157
];
148158
}
149-
return b.call(b.thunk(b.block(statements)));
159+
return maybe_skip_ownership_validation(b.call(b.thunk(b.block(statements))));
150160
}
151161
}
152162
}

packages/svelte/src/compiler/phases/3-transform/client/visitors/javascript-runes.js

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
/** @import { CallExpression, Expression, Identifier, Literal, MethodDefinition, PrivateIdentifier, PropertyDefinition, VariableDeclarator } from 'estree' */
22
/** @import { Binding } from '#compiler' */
33
/** @import { ComponentVisitors, StateField } from '../types.js' */
4+
import { dev, is_ignored } from '../../../../state.js';
5+
import * as assert from '../../../../utils/assert.js';
6+
import { extract_paths } from '../../../../utils/ast.js';
7+
import * as b from '../../../../utils/builders.js';
8+
import { regex_invalid_identifier_chars } from '../../../patterns.js';
49
import { get_rune } from '../../../scope.js';
510
import { is_hoistable_function, transform_inspect_rune } from '../../utils.js';
6-
import * as b from '../../../../utils/builders.js';
7-
import * as assert from '../../../../utils/assert.js';
811
import {
912
get_prop_source,
1013
is_prop_source,
1114
is_state_source,
1215
serialize_proxy_reassignment,
1316
should_proxy_or_freeze
1417
} from '../utils.js';
15-
import { extract_paths } from '../../../../utils/ast.js';
16-
import { regex_invalid_identifier_chars } from '../../../patterns.js';
17-
import { dev } from '../../../../state.js';
1818

1919
/** @type {ComponentVisitors} */
2020
export const javascript_visitors_runes = {
@@ -197,7 +197,9 @@ export const javascript_visitors_runes = {
197197
b.call(
198198
'$.add_owner',
199199
b.call('$.get', b.member(b.this, b.private_id(name))),
200-
b.id('owner')
200+
b.id('owner'),
201+
b.literal(false),
202+
is_ignored(node, 'ownership_invalid_binding') && b.true
201203
)
202204
)
203205
),
@@ -446,7 +448,11 @@ export const javascript_visitors_runes = {
446448
}
447449

448450
if (rune === '$state.snapshot') {
449-
return b.call('$.snapshot', /** @type {Expression} */ (context.visit(node.arguments[0])));
451+
return b.call(
452+
'$.snapshot',
453+
/** @type {Expression} */ (context.visit(node.arguments[0])),
454+
is_ignored(node, 'state_snapshot_uncloneable') && b.true
455+
);
450456
}
451457

452458
if (rune === '$state.is') {

packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js

Lines changed: 65 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,26 @@
33
/** @import { SourceLocation } from '#shared' */
44
/** @import { Scope } from '../../../scope.js' */
55
/** @import { ComponentClientTransformState, ComponentContext, ComponentVisitors } from '../types.js' */
6+
import is_reference from 'is-reference';
7+
import { walk } from 'zimmerframe';
8+
import {
9+
AttributeAliases,
10+
DOMBooleanAttributes,
11+
EACH_INDEX_REACTIVE,
12+
EACH_IS_ANIMATED,
13+
EACH_IS_CONTROLLED,
14+
EACH_IS_STRICT_EQUALS,
15+
EACH_ITEM_REACTIVE,
16+
EACH_KEYED,
17+
is_capture_event,
18+
TEMPLATE_FRAGMENT,
19+
TEMPLATE_USE_IMPORT_NODE,
20+
TRANSITION_GLOBAL,
21+
TRANSITION_IN,
22+
TRANSITION_OUT
23+
} from '../../../../../constants.js';
24+
import { escape_html } from '../../../../../escaping.js';
25+
import { dev, is_ignored, locator } from '../../../../state.js';
626
import {
727
extract_identifiers,
828
extract_paths,
@@ -13,48 +33,28 @@ import {
1333
object,
1434
unwrap_optional
1535
} from '../../../../utils/ast.js';
36+
import * as b from '../../../../utils/builders.js';
37+
import { sanitize_template_string } from '../../../../utils/sanitize_template_string.js';
1638
import { binding_properties } from '../../../bindings.js';
17-
import { clean_nodes, determine_namespace_for_children, infer_namespace } from '../../utils.js';
1839
import {
1940
DOMProperties,
2041
LoadErrorElements,
2142
PassiveEvents,
2243
VoidElements
2344
} from '../../../constants.js';
2445
import { is_custom_element_node, is_element_node } from '../../../nodes.js';
25-
import * as b from '../../../../utils/builders.js';
46+
import { regex_is_valid_identifier } from '../../../patterns.js';
47+
import { clean_nodes, determine_namespace_for_children, infer_namespace } from '../../utils.js';
2648
import {
27-
with_loc,
49+
create_derived,
50+
create_derived_block_argument,
2851
function_visitor,
2952
get_assignment_value,
3053
serialize_get_binding,
3154
serialize_set_binding,
32-
create_derived,
33-
create_derived_block_argument
55+
with_loc
3456
} from '../utils.js';
35-
import {
36-
AttributeAliases,
37-
DOMBooleanAttributes,
38-
EACH_INDEX_REACTIVE,
39-
EACH_IS_ANIMATED,
40-
EACH_IS_CONTROLLED,
41-
EACH_IS_STRICT_EQUALS,
42-
EACH_ITEM_REACTIVE,
43-
EACH_KEYED,
44-
is_capture_event,
45-
TEMPLATE_FRAGMENT,
46-
TEMPLATE_USE_IMPORT_NODE,
47-
TRANSITION_GLOBAL,
48-
TRANSITION_IN,
49-
TRANSITION_OUT
50-
} from '../../../../../constants.js';
51-
import { escape_html } from '../../../../../escaping.js';
52-
import { regex_is_valid_identifier } from '../../../patterns.js';
5357
import { javascript_visitors_runes } from './javascript-runes.js';
54-
import { sanitize_template_string } from '../../../../utils/sanitize_template_string.js';
55-
import { walk } from 'zimmerframe';
56-
import { dev, locator } from '../../../../state.js';
57-
import is_reference from 'is-reference';
5858

5959
/**
6060
* @param {RegularElement | SvelteElement} element
@@ -324,7 +324,8 @@ function serialize_element_spread_attributes(
324324
b.id(id),
325325
b.object(values),
326326
lowercase_attributes,
327-
b.literal(context.state.analysis.css.hash)
327+
b.literal(context.state.analysis.css.hash),
328+
is_ignored(element, 'hydration_attribute_changed') && b.true
328329
)
329330
)
330331
);
@@ -489,7 +490,15 @@ function serialize_element_attribute_update_assignment(element, node_id, attribu
489490

490491
// The foreign namespace doesn't have any special handling, everything goes through the attr function
491492
if (context.state.metadata.namespace === 'foreign') {
492-
const statement = b.stmt(b.call('$.set_attribute', node_id, b.literal(name), value));
493+
const statement = b.stmt(
494+
b.call(
495+
'$.set_attribute',
496+
node_id,
497+
b.literal(name),
498+
value,
499+
is_ignored(element, 'hydration_attribute_changed') && b.true
500+
)
501+
);
493502

494503
if (attribute.metadata.expression.has_state) {
495504
const id = state.scope.generate(`${node_id.name}_${name}`);
@@ -525,7 +534,15 @@ function serialize_element_attribute_update_assignment(element, node_id, attribu
525534
update = b.stmt(b.assignment('=', b.member(node_id, b.id(name)), value));
526535
} else {
527536
const callee = name.startsWith('xlink') ? '$.set_xlink_attribute' : '$.set_attribute';
528-
update = b.stmt(b.call(callee, node_id, b.literal(name), value));
537+
update = b.stmt(
538+
b.call(
539+
callee,
540+
node_id,
541+
b.literal(name),
542+
value,
543+
is_ignored(element, 'hydration_attribute_changed') && b.true
544+
)
545+
);
529546
}
530547

531548
if (attribute.metadata.expression.has_state) {
@@ -780,7 +797,12 @@ function serialize_inline_component(node, component_name, context, anchor = cont
780797
} else if (attribute.type === 'BindDirective') {
781798
const expression = /** @type {Expression} */ (context.visit(attribute.expression));
782799

783-
if (dev && expression.type === 'MemberExpression' && context.state.analysis.runes) {
800+
if (
801+
dev &&
802+
expression.type === 'MemberExpression' &&
803+
context.state.analysis.runes &&
804+
!is_ignored(node, 'binding_property_non_reactive')
805+
) {
784806
context.state.init.push(serialize_validate_binding(context.state, attribute, expression));
785807
}
786808

@@ -789,7 +811,14 @@ function serialize_inline_component(node, component_name, context, anchor = cont
789811
} else {
790812
if (dev) {
791813
binding_initializers.push(
792-
b.stmt(b.call(b.id('$.add_owner_effect'), b.thunk(expression), b.id(component_name)))
814+
b.stmt(
815+
b.call(
816+
b.id('$.add_owner_effect'),
817+
b.thunk(expression),
818+
b.id(component_name),
819+
is_ignored(node, 'ownership_invalid_binding') && b.true
820+
)
821+
)
793822
);
794823
}
795824

@@ -1811,7 +1840,8 @@ export const template_visitors = {
18111840
context.state.node,
18121841
b.thunk(/** @type {Expression} */ (context.visit(node.expression))),
18131842
b.literal(context.state.metadata.namespace === 'svg'),
1814-
b.literal(context.state.metadata.namespace === 'mathml')
1843+
b.literal(context.state.metadata.namespace === 'mathml'),
1844+
is_ignored(node, 'hydration_html_changed') && b.true
18151845
)
18161846
)
18171847
);
@@ -2903,7 +2933,8 @@ export const template_visitors = {
29032933
type === 'KeyBlock'
29042934
)) &&
29052935
dev &&
2906-
context.state.analysis.runes
2936+
context.state.analysis.runes &&
2937+
!is_ignored(node, 'binding_property_non_reactive')
29072938
) {
29082939
context.state.init.push(
29092940
serialize_validate_binding(

0 commit comments

Comments
 (0)