Merge branch 'sveltejs:master' into master

pull/7107/head
Michael Leahy 5 years ago committed by GitHub
commit ebec1500e9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,8 +1,13 @@
# Svelte changelog
## Unreleased
## 3.45.0
* Fix non-boolean attribute rendering in SSR to render truthy values as-is ([#6121](https://github.com/sveltejs/svelte/issues/6121))
* Fix binding to a member expression also invalidating the member property ([#6921](https://github.com/sveltejs/svelte/issues/6921))
* Fix default values in `{#each}`/etc. destructurings not being considered references for the purposes of compiler warnings ([#6964](https://github.com/sveltejs/svelte/issues/6964))
* Fix `{:else if}` value incorrectly being cached ([#7043](https://github.com/sveltejs/svelte/pull/7043))
* Add `a11y-no-redundant-roles` warning ([#7067](https://github.com/sveltejs/svelte/pull/7067))
* Fix code generation error with arrow functions whose bodies are object destructuring assignments ([#7087](https://github.com/sveltejs/svelte/issues/7087))
## 3.44.3

18
package-lock.json generated

@ -1,12 +1,12 @@
{
"name": "svelte",
"version": "3.44.3",
"version": "3.45.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "svelte",
"version": "3.44.3",
"version": "3.45.0",
"license": "MIT",
"devDependencies": {
"@ampproject/remapping": "^0.3.0",
@ -25,7 +25,7 @@
"acorn": "^8.4.1",
"agadoo": "^1.1.0",
"c8": "^5.0.1",
"code-red": "^0.2.3",
"code-red": "^0.2.4",
"codecov": "^3.5.0",
"css-tree": "^1.1.2",
"eslint": "^7.32.0",
@ -1082,9 +1082,9 @@
}
},
"node_modules/code-red": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/code-red/-/code-red-0.2.3.tgz",
"integrity": "sha512-l9MRiYO9iNx3dCpoZBZkaHAVtbhig8TBddEHq7ssWcZRAjaYR8NoRFzZ56VJ20TIg7hEQegCVCH3fVus+2Ol4Q==",
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/code-red/-/code-red-0.2.4.tgz",
"integrity": "sha512-tAJQiZviSyB2KUhz+rocKFzCHPkVooX2aFrdpfWDRvxWJaBQTYFJ/Z2TcWqbjXj5oJJBlqd2GxBXdtAhOXySVQ==",
"dev": true,
"dependencies": {
"@types/estree": "^0.0.50",
@ -6376,9 +6376,9 @@
}
},
"code-red": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/code-red/-/code-red-0.2.3.tgz",
"integrity": "sha512-l9MRiYO9iNx3dCpoZBZkaHAVtbhig8TBddEHq7ssWcZRAjaYR8NoRFzZ56VJ20TIg7hEQegCVCH3fVus+2Ol4Q==",
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/code-red/-/code-red-0.2.4.tgz",
"integrity": "sha512-tAJQiZviSyB2KUhz+rocKFzCHPkVooX2aFrdpfWDRvxWJaBQTYFJ/Z2TcWqbjXj5oJJBlqd2GxBXdtAhOXySVQ==",
"dev": true,
"requires": {
"@types/estree": "^0.0.50",

@ -1,6 +1,6 @@
{
"name": "svelte",
"version": "3.44.3",
"version": "3.45.0",
"description": "Cybernetically enhanced web apps",
"module": "index.mjs",
"main": "index",
@ -132,7 +132,7 @@
"acorn": "^8.4.1",
"agadoo": "^1.1.0",
"c8": "^5.0.1",
"code-red": "^0.2.3",
"code-red": "^0.2.4",
"codecov": "^3.5.0",
"css-tree": "^1.1.2",
"eslint": "^7.32.0",

@ -33,12 +33,12 @@ export default class AwaitBlock extends Node {
if (this.then_node) {
this.then_contexts = [];
unpack_destructuring(this.then_contexts, info.value);
unpack_destructuring({ contexts: this.then_contexts, node: info.value, scope, component });
}
if (this.catch_node) {
this.catch_contexts = [];
unpack_destructuring(this.catch_contexts, info.error);
unpack_destructuring({ contexts: this.catch_contexts, node: info.error, scope, component });
}
this.pending = new PendingBlock(component, this, scope, info.pending);

@ -39,7 +39,7 @@ export default class EachBlock extends AbstractBlock {
this.scope = scope.child();
this.contexts = [];
unpack_destructuring(this.contexts, info.context);
unpack_destructuring({ contexts: this.contexts, node: info.context, scope, component });
this.contexts.forEach(context => {
this.scope.add(context.key.name, this.expression.dependencies, this);

@ -3,6 +3,9 @@ import { Node, Identifier, Expression } from 'estree';
import { walk } from 'estree-walker';
import is_reference, { NodeWithPropertyDefinition } from 'is-reference';
import { clone } from '../../../utils/clone';
import Component from '../../Component';
import flatten_reference from '../../utils/flatten_reference';
import TemplateScope from './TemplateScope';
export interface Context {
key: Identifier;
@ -11,7 +14,21 @@ export interface Context {
default_modifier: (node: Node, to_ctx: (name: string) => Node) => Node;
}
export function unpack_destructuring(contexts: Context[], node: Node, modifier: Context['modifier'] = node => node, default_modifier: Context['default_modifier'] = node => node) {
export function unpack_destructuring({
contexts,
node,
modifier = (node) => node,
default_modifier = (node) => node,
scope,
component
}: {
contexts: Context[];
node: Node;
modifier?: Context['modifier'];
default_modifier?: Context['default_modifier'];
scope: TemplateScope;
component: Component;
}) {
if (!node) return;
if (node.type === 'Identifier') {
@ -29,13 +46,41 @@ export function unpack_destructuring(contexts: Context[], node: Node, modifier:
} else if (node.type === 'ArrayPattern') {
node.elements.forEach((element, i) => {
if (element && element.type === 'RestElement') {
unpack_destructuring(contexts, element, node => x`${modifier(node)}.slice(${i})` as Node, default_modifier);
unpack_destructuring({
contexts,
node: element,
modifier: (node) => x`${modifier(node)}.slice(${i})` as Node,
default_modifier,
scope,
component
});
} else if (element && element.type === 'AssignmentPattern') {
const n = contexts.length;
mark_referenced(element.right, scope, component);
unpack_destructuring(contexts, element.left, node => x`${modifier(node)}[${i}]`, (node, to_ctx) => x`${node} !== undefined ? ${node} : ${update_reference(contexts, n, element.right, to_ctx)}` as Node);
unpack_destructuring({
contexts,
node: element.left,
modifier: (node) => x`${modifier(node)}[${i}]`,
default_modifier: (node, to_ctx) =>
x`${node} !== undefined ? ${node} : ${update_reference(
contexts,
n,
element.right,
to_ctx
)}` as Node,
scope,
component
});
} else {
unpack_destructuring(contexts, element, node => x`${modifier(node)}[${i}]` as Node, default_modifier);
unpack_destructuring({
contexts,
node: element,
modifier: (node) => x`${modifier(node)}[${i}]` as Node,
default_modifier,
scope,
component
});
}
});
} else if (node.type === 'ObjectPattern') {
@ -43,12 +88,17 @@ export function unpack_destructuring(contexts: Context[], node: Node, modifier:
node.properties.forEach((property) => {
if (property.type === 'RestElement') {
unpack_destructuring(
unpack_destructuring({
contexts,
property.argument,
node => x`@object_without_properties(${modifier(node)}, [${used_properties}])` as Node,
default_modifier
);
node: property.argument,
modifier: (node) =>
x`@object_without_properties(${modifier(
node
)}, [${used_properties}])` as Node,
default_modifier,
scope,
component
});
} else {
const key = property.key as Identifier;
const value = property.value;
@ -57,16 +107,43 @@ export function unpack_destructuring(contexts: Context[], node: Node, modifier:
if (value.type === 'AssignmentPattern') {
const n = contexts.length;
unpack_destructuring(contexts, value.left, node => x`${modifier(node)}.${key.name}`, (node, to_ctx) => x`${node} !== undefined ? ${node} : ${update_reference(contexts, n, value.right, to_ctx)}` as Node);
mark_referenced(value.right, scope, component);
unpack_destructuring({
contexts,
node: value.left,
modifier: (node) => x`${modifier(node)}.${key.name}`,
default_modifier: (node, to_ctx) =>
x`${node} !== undefined ? ${node} : ${update_reference(
contexts,
n,
value.right,
to_ctx
)}` as Node,
scope,
component
});
} else {
unpack_destructuring(contexts, value, node => x`${modifier(node)}.${key.name}` as Node, default_modifier);
unpack_destructuring({
contexts,
node: value,
modifier: (node) => x`${modifier(node)}.${key.name}` as Node,
default_modifier,
scope,
component
});
}
}
});
}
}
function update_reference(contexts: Context[], n: number, expression: Expression, to_ctx: (name: string) => Node): Node {
function update_reference(
contexts: Context[],
n: number,
expression: Expression,
to_ctx: (name: string) => Node
): Node {
const find_from_context = (node: Identifier) => {
for (let i = n; i < contexts.length; i++) {
const { key } = contexts[i];
@ -85,7 +162,12 @@ function update_reference(contexts: Context[], n: number, expression: Expression
expression = clone(expression) as Expression;
walk(expression, {
enter(node, parent: Node) {
if (is_reference(node as NodeWithPropertyDefinition, parent as NodeWithPropertyDefinition)) {
if (
is_reference(
node as NodeWithPropertyDefinition,
parent as NodeWithPropertyDefinition
)
) {
this.replace(find_from_context(node as Identifier));
this.skip();
}
@ -94,3 +176,20 @@ function update_reference(contexts: Context[], n: number, expression: Expression
return expression;
}
function mark_referenced(
node: Node,
scope: TemplateScope,
component: Component
) {
walk(node, {
enter(node: any, parent: any) {
if (is_reference(node, parent)) {
const { name } = flatten_reference(node);
if (!scope.is_let(name) && !scope.names.has(name)) {
component.add_reference(name);
}
}
}
});
}

@ -70,6 +70,30 @@ export default class BindingWrapper {
return dependencies;
}
get_update_dependencies() {
const object = this.object;
const dependencies = new Set<string>();
if (this.node.expression.template_scope.names.has(object)) {
this.node.expression.template_scope.dependencies_for_name
.get(object)
.forEach((name) => dependencies.add(name));
} else {
dependencies.add(object);
}
const result = new Set(dependencies);
dependencies.forEach((dependency) => {
const indirect_dependencies = this.parent.renderer.component.indirect_dependencies.get(dependency);
if (indirect_dependencies) {
indirect_dependencies.forEach(indirect_dependency => {
result.add(indirect_dependency);
});
}
});
return result;
}
is_readonly_media_attribute() {
return this.node.is_readonly_media_attribute();
}

@ -466,7 +466,7 @@ export default class ElementWrapper extends Wrapper {
binding_group.bindings.forEach(binding => {
// TODO this is a mess
add_to_set(dependencies, binding.get_dependencies());
add_to_set(dependencies, binding.get_update_dependencies());
add_to_set(contextual_dependencies, binding.handler.contextual_dependencies);
binding.render(block, lock);

@ -266,15 +266,15 @@ export default class IfBlockWrapper extends Wrapper {
if (this.needs_update) {
block.chunks.init.push(b`
function ${select_block_type}(#ctx, #dirty) {
${this.branches.map(({ dependencies, condition, snippet, block }) => condition
${this.branches.map(({ dependencies, condition, snippet }) => {
return b`${snippet && dependencies.length > 0 ? b`if (${block.renderer.dirty(dependencies)}) ${condition} = null;` : null}`;
})}
${this.branches.map(({ condition, snippet, block }) => condition
? b`
${snippet && (
dependencies.length > 0
? b`if (${condition} == null || ${block.renderer.dirty(dependencies)}) ${condition} = !!${snippet}`
: b`if (${condition} == null) ${condition} = !!${snippet}`
${snippet && b`if (${condition} == null) ${condition} = !!${snippet}`}
if (${condition}) return ${block.name};`
: b`return ${block.name};`
)}
if (${condition}) return ${block.name};`
: b`return ${block.name};`)}
}
`);
} else {
@ -387,13 +387,12 @@ export default class IfBlockWrapper extends Wrapper {
${this.needs_update
? b`
function ${select_block_type}(#ctx, #dirty) {
${this.branches.map(({ dependencies, condition, snippet }, i) => condition
${this.branches.map(({ dependencies, condition, snippet }) => {
return b`${snippet && dependencies.length > 0 ? b`if (${block.renderer.dirty(dependencies)}) ${condition} = null;` : null}`;
})}
${this.branches.map(({ condition, snippet }, i) => condition
? b`
${snippet && (
dependencies.length > 0
? b`if (${condition} == null || ${block.renderer.dirty(dependencies)}) ${condition} = !!${snippet}`
: b`if (${condition} == null) ${condition} = !!${snippet}`
)}
${snippet && b`if (${condition} == null) ${condition} = !!${snippet}`}
if (${condition}) return ${i};`
: b`return ${i};`)}
${!has_else && b`return -1;`}

@ -12,7 +12,7 @@ export default function bind_this(component: Component, block: Block, binding: B
const callee = block.renderer.reference(fn.name);
const { contextual_dependencies, mutation } = binding.handler;
const dependencies = binding.get_dependencies();
const dependencies = binding.get_update_dependencies();
const body = b`
${mutation}

@ -141,7 +141,7 @@ export function create_ssr_component(fn) {
export function add_attribute(name, value, boolean) {
if (value == null || (boolean && !value)) return '';
return ` ${name}${value === true ? '' : `=${typeof value === 'string' ? JSON.stringify(escape(value)) : `"${value}"`}`}`;
return ` ${name}${value === true && boolean_attributes.has(name) ? '' : `=${typeof value === 'string' ? JSON.stringify(escape(value)) : `"${value}"`}`}`;
}
export function add_classes(classes) {

@ -1,7 +1,8 @@
export default {
html: '<textarea readonly></textarea>',
html: '<textarea readonly data-attr="true"></textarea>',
test({ assert, target }) {
const textarea = target.querySelector('textarea');
assert.equal(textarea.dataset.attr, 'true');
assert.ok(textarea.readOnly);
}
};

@ -1 +1 @@
<textarea readonly="{true}"></textarea>
<textarea readonly={true} data-attr={true} />

@ -0,0 +1,18 @@
// binding member expression shouldn't invalidate the property name
export default {
async test({ assert, component, target, window }) {
const input = target.querySelector('input');
assert.deepEqual(component.logs.length, 1);
assert.equal(input.value, 'abc');
input.value = 'hij';
await input.dispatchEvent(new window.Event('input'));
assert.deepEqual(component.values.a, 'hij');
assert.deepEqual(component.logs.length, 1);
component.paths = ['b'];
assert.deepEqual(component.logs.length, 2);
assert.equal(input.value, 'def');
}
};

@ -0,0 +1,9 @@
<script>
export let values = { a: 'abc', b: 'def' };
export let paths = ['a'];
export let logs = [];
$: paths && logs.push('paths updated');
</script>
<input bind:value={values[paths[0]]} />

@ -0,0 +1,10 @@
export default {
html: '<div>content</div><div>content</div><div>content</div>',
test({ assert, target, component }) {
const divs = target.querySelectorAll('div');
assert.equal(component.refs[0], divs[0]);
assert.equal(component.refs[1], divs[1]);
assert.equal(component.refs[2], divs[2]);
}
};

@ -0,0 +1,15 @@
<script>
export let data = [ { id: '1' }, { id: '2' }, { id: '3' } ];
export let refs = [];
// note that this is NOT data.slice().reverse()
// as that wouldn't have triggered an infinite loop
$: list = data.reverse();
</script>
{#each list as { id }, index (id)}
<div bind:this={refs[index]}>
content
</div>
{/each}

@ -0,0 +1,8 @@
// binding member expression shouldn't invalidate the property name
export default {
test({ assert, component, target }) {
const div = target.querySelector('div');
assert.equal(div, component.container.a);
assert.deepEqual(component.logs.length, 1);
}
};

@ -0,0 +1,9 @@
<script>
export let container = {};
export let paths = ['a'];
export let logs = [];
$: paths && logs.push('paths updated');
</script>
<div bind:this={container[paths[0]]} />

@ -0,0 +1,46 @@
export default {
async test({ assert, component, target, window }) {
const [btn1, btn2] = target.querySelectorAll('button');
const clickEvent = new window.MouseEvent('click');
await btn2.dispatchEvent(clickEvent);
assert.htmlEqual(target.innerHTML, `
<button>Toggle foo</button>
<button>Toggle bar</button>
<hr>
foo: false, bar: true
<hr>
bar!
`);
await btn1.dispatchEvent(clickEvent);
assert.htmlEqual(target.innerHTML, `
<button>Toggle foo</button>
<button>Toggle bar</button>
<hr>
foo: true, bar: true
<hr>
foo!
`);
await btn2.dispatchEvent(clickEvent);
assert.htmlEqual(target.innerHTML, `
<button>Toggle foo</button>
<button>Toggle bar</button>
<hr>
foo: true, bar: false
<hr>
foo!
`);
await btn1.dispatchEvent(clickEvent);
assert.htmlEqual(target.innerHTML, `
<button>Toggle foo</button>
<button>Toggle bar</button>
<hr>
foo: false, bar: false
<hr>
`);
}
};

@ -0,0 +1,21 @@
<script>
let foo = false
let bar = [false];
</script>
<button on:click={() => foo = !foo}>
Toggle foo
</button>
<button on:click={() => bar[0] = !bar[0]}>
Toggle bar
</button>
<hr>
{@html `foo: ${foo}, bar: ${bar.every(x => x)}`}
<hr>
{#if foo}
foo!
{:else if bar.every(x => x)}
bar!
{/if}

@ -0,0 +1,20 @@
<script>
let Component;
export let array;
export let default_value_1;
export let default_value_2;
export let default_value_3;
export let default_value_4;
export let default_value_5;
export let default_value_6;
</script>
<Component>
<svelte:fragment let:a={default_value_6}>
{#each array as default_value_5}
{#each array as { a = default_value_1, b: { b = [default_value_2, default_value_3], c: [c = default_value_4] }, d = default_value_5, e = default_value_6 }}
{a}{b}{c}{d}{e}
{/each}
{/each}
</svelte:fragment>
</Component>

@ -0,0 +1,32 @@
[
{
"code": "unused-export-let",
"message": "Component_1 has unused export property 'default_value_5'. If it is for external reference only, please consider using `export const default_value_5`",
"pos": 172,
"start": {
"character": 172,
"column": 12,
"line": 8
},
"end": {
"character": 187,
"column": 27,
"line": 8
}
},
{
"code": "unused-export-let",
"message": "Component_1 has unused export property 'default_value_6'. If it is for external reference only, please consider using `export const default_value_6`",
"pos": 201,
"start": {
"character": 201,
"column": 12,
"line": 9
},
"end": {
"character": 216,
"column": 27,
"line": 9
}
}
]
Loading…
Cancel
Save