From ff5f25249e39985b6ca88c6815d123f26e40402a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maur=C3=ADcio=20Kishi?= Date: Mon, 20 Apr 2020 10:05:37 -0300 Subject: [PATCH 01/69] support dimension bindings in cross-origin mode (#2989) --- .../render_dom/wrappers/Element/index.ts | 2 +- src/runtime/internal/dom.ts | 74 ++++++++++++------- test/js/samples/bind-width-height/expected.js | 2 +- test/js/samples/video-bindings/expected.js | 2 +- .../binding-width-height-a11y/_config.js | 6 +- 5 files changed, 55 insertions(+), 31 deletions(-) diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index 9291f329b6..64d02191fb 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -586,7 +586,7 @@ export default class ElementWrapper extends Wrapper { ); block.chunks.destroy.push( - b`${resize_listener}.cancel();` + b`${resize_listener}();` ); } else { block.event_listeners.push( diff --git a/src/runtime/internal/dom.ts b/src/runtime/internal/dom.ts index 9ab9a4395f..bf8fc1f5a0 100644 --- a/src/runtime/internal/dom.ts +++ b/src/runtime/internal/dom.ts @@ -57,7 +57,7 @@ export function empty() { return text(''); } -export function listen(node: Node, event: string, handler: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions | EventListenerOptions) { +export function listen(node: EventTarget, event: string, handler: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions | EventListenerOptions) { node.addEventListener(event, handler, options); return () => node.removeEventListener(event, handler, options); } @@ -234,37 +234,61 @@ export function select_multiple_value(select) { return [].map.call(select.querySelectorAll(':checked'), option => option.__value); } -export function add_resize_listener(element, fn) { - if (getComputedStyle(element).position === 'static') { - element.style.position = 'relative'; +// unfortunately this can't be a constant as that wouldn't be tree-shakeable +// so we cache the result instead +let crossorigin: boolean; + +export function is_crossorigin() { + if (crossorigin === undefined) { + crossorigin = false; + + try { + if (typeof window !== 'undefined' && window.parent) { + void window.parent.document; + } + } catch (error) { + crossorigin = true; + } } - const object = document.createElement('object'); - object.setAttribute('style', 'display: block; position: absolute; top: 0; left: 0; height: 100%; width: 100%; overflow: hidden; pointer-events: none; z-index: -1;'); - object.setAttribute('aria-hidden', 'true'); - object.type = 'text/html'; - object.tabIndex = -1; + return crossorigin; +} - let win; +export function add_resize_listener(node: HTMLElement, fn: () => void) { + const computed_style = getComputedStyle(node); + const z_index = (parseInt(computed_style.zIndex) || 0) - 1; - object.onload = () => { - win = object.contentDocument.defaultView; - win.addEventListener('resize', fn); - }; + if (computed_style.position === 'static') { + node.style.position = 'relative'; + } - if (/Trident/.test(navigator.userAgent)) { - element.appendChild(object); - object.data = 'about:blank'; + const iframe = element('iframe'); + iframe.setAttribute('style', + `display: block; position: absolute; top: 0; left: 0; width: 100%; height: 100%; ` + + `overflow: hidden; border: 0; opacity: 0; pointer-events: none; z-index: ${z_index};` + ); + iframe.setAttribute('aria-hidden', 'true'); + iframe.tabIndex = -1; + + let unsubscribe: () => void; + + if (is_crossorigin()) { + iframe.src = `data:text/html,`; + unsubscribe = listen(window, 'message', (event: MessageEvent) => { + if (event.source === iframe.contentWindow) fn(); + }); } else { - object.data = 'about:blank'; - element.appendChild(object); + iframe.src = 'about:blank'; + iframe.onload = () => { + unsubscribe = listen(iframe.contentWindow, 'resize', fn); + }; } - return { - cancel: () => { - win && win.removeEventListener && win.removeEventListener('resize', fn); - element.removeChild(object); - } + append(node, iframe); + + return () => { + detach(iframe); + if (unsubscribe) unsubscribe(); }; } @@ -316,4 +340,4 @@ export class HtmlTag { d() { this.n.forEach(detach); } -} \ No newline at end of file +} diff --git a/test/js/samples/bind-width-height/expected.js b/test/js/samples/bind-width-height/expected.js index 5c1888b7db..4848704c4b 100644 --- a/test/js/samples/bind-width-height/expected.js +++ b/test/js/samples/bind-width-height/expected.js @@ -30,7 +30,7 @@ function create_fragment(ctx) { o: noop, d(detaching) { if (detaching) detach(div); - div_resize_listener.cancel(); + div_resize_listener(); } }; } diff --git a/test/js/samples/video-bindings/expected.js b/test/js/samples/video-bindings/expected.js index 3cad17b34d..9c5467e1a0 100644 --- a/test/js/samples/video-bindings/expected.js +++ b/test/js/samples/video-bindings/expected.js @@ -59,7 +59,7 @@ function create_fragment(ctx) { o: noop, d(detaching) { if (detaching) detach(video); - video_resize_listener.cancel(); + video_resize_listener(); run_all(dispose); } }; diff --git a/test/runtime/samples/binding-width-height-a11y/_config.js b/test/runtime/samples/binding-width-height-a11y/_config.js index 5ab4a7ade7..93856bf0f6 100644 --- a/test/runtime/samples/binding-width-height-a11y/_config.js +++ b/test/runtime/samples/binding-width-height-a11y/_config.js @@ -1,8 +1,8 @@ export default { async test({ assert, target }) { - const object = target.querySelector('object'); + const iframe = target.querySelector('iframe'); - assert.equal(object.getAttribute('aria-hidden'), "true"); - assert.equal(object.getAttribute('tabindex'), "-1"); + assert.equal(iframe.getAttribute('aria-hidden'), "true"); + assert.equal(iframe.getAttribute('tabindex'), "-1"); } }; From fe003b57cb3076c1fc7d94b45a79ff397733186a Mon Sep 17 00:00:00 2001 From: Conduitry Date: Mon, 20 Apr 2020 09:08:16 -0400 Subject: [PATCH 02/69] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 80db122cb6..dbad13d01f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased +* Support dimension bindings in cross-origin environments ([#2147](https://github.com/sveltejs/svelte/issues/2147)) * Try using `globalThis` rather than `globals` for the benefit of non-Node servers and web workers ([#3561](https://github.com/sveltejs/svelte/issues/3561), [#4545](https://github.com/sveltejs/svelte/issues/4545)) * Fix attaching of JS debugging comments to HTML comments ([#4565](https://github.com/sveltejs/svelte/issues/4565)) From 77ec48debaf99e33197729d3c739d888aa7654d4 Mon Sep 17 00:00:00 2001 From: Tan Li Hau Date: Mon, 20 Apr 2020 21:27:21 +0800 Subject: [PATCH 03/69] add await catch shorthand (#4490) --- CHANGELOG.md | 1 + src/compiler/parse/state/mustache.ts | 10 +++++ .../samples/await-catch-shorthand/_config.js | 41 +++++++++++++++++++ .../samples/await-catch-shorthand/main.svelte | 7 ++++ .../await-shorthand-no-then/input.svelte | 7 ++++ .../await-shorthand-no-then/warnings.json | 1 + 6 files changed, 67 insertions(+) create mode 100644 test/runtime/samples/await-catch-shorthand/_config.js create mode 100644 test/runtime/samples/await-catch-shorthand/main.svelte create mode 100644 test/validator/samples/await-shorthand-no-then/input.svelte create mode 100644 test/validator/samples/await-shorthand-no-then/warnings.json diff --git a/CHANGELOG.md b/CHANGELOG.md index dbad13d01f..ca47ad51b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * Support dimension bindings in cross-origin environments ([#2147](https://github.com/sveltejs/svelte/issues/2147)) * Try using `globalThis` rather than `globals` for the benefit of non-Node servers and web workers ([#3561](https://github.com/sveltejs/svelte/issues/3561), [#4545](https://github.com/sveltejs/svelte/issues/4545)) +* Support `{#await ... catch ...}` syntax shorthand ([#3623](https://github.com/sveltejs/svelte/issues/3623)) * Fix attaching of JS debugging comments to HTML comments ([#4565](https://github.com/sveltejs/svelte/issues/4565)) ## 3.20.1 diff --git a/src/compiler/parse/state/mustache.ts b/src/compiler/parse/state/mustache.ts index 28ef853f8e..0f6608f679 100644 --- a/src/compiler/parse/state/mustache.ts +++ b/src/compiler/parse/state/mustache.ts @@ -309,6 +309,13 @@ export default function mustache(parser: Parser) { parser.allow_whitespace(); } + const await_block_catch_shorthand = !await_block_shorthand && type === 'AwaitBlock' && parser.eat('catch'); + if (await_block_catch_shorthand) { + parser.require_whitespace(); + block.error = parser.read_destructure_pattern(); + parser.allow_whitespace(); + } + parser.eat('}', true); parser.current().children.push(block); @@ -319,6 +326,9 @@ export default function mustache(parser: Parser) { if (await_block_shorthand) { block.then.skip = false; child_block = block.then; + } else if (await_block_catch_shorthand) { + block.catch.skip = false; + child_block = block.catch; } else { block.pending.skip = false; child_block = block.pending; diff --git a/test/runtime/samples/await-catch-shorthand/_config.js b/test/runtime/samples/await-catch-shorthand/_config.js new file mode 100644 index 0000000000..2e1574145f --- /dev/null +++ b/test/runtime/samples/await-catch-shorthand/_config.js @@ -0,0 +1,41 @@ +let fulfil; + +let thePromise = new Promise(f => { + fulfil = f; +}); + +export default { + props: { + thePromise + }, + + html: ``, + + test({ assert, component, target }) { + fulfil(42); + + return thePromise + .then(() => { + assert.htmlEqual(target.innerHTML, ``); + + let reject; + + thePromise = new Promise((f, r) => { + reject = r; + }); + + component.thePromise = thePromise; + + assert.htmlEqual(target.innerHTML, ``); + + reject(new Error('something broke')); + + return thePromise.catch(() => {}); + }) + .then(() => { + assert.htmlEqual(target.innerHTML, ` +

oh no! something broke

+ `); + }); + } +}; \ No newline at end of file diff --git a/test/runtime/samples/await-catch-shorthand/main.svelte b/test/runtime/samples/await-catch-shorthand/main.svelte new file mode 100644 index 0000000000..844e7da08c --- /dev/null +++ b/test/runtime/samples/await-catch-shorthand/main.svelte @@ -0,0 +1,7 @@ + + +{#await thePromise catch theError} +

oh no! {theError.message}

+{/await} \ No newline at end of file diff --git a/test/validator/samples/await-shorthand-no-then/input.svelte b/test/validator/samples/await-shorthand-no-then/input.svelte new file mode 100644 index 0000000000..6e8a99f131 --- /dev/null +++ b/test/validator/samples/await-shorthand-no-then/input.svelte @@ -0,0 +1,7 @@ + + +{#await promise catch error} +

Error: {error}

+{/await} diff --git a/test/validator/samples/await-shorthand-no-then/warnings.json b/test/validator/samples/await-shorthand-no-then/warnings.json new file mode 100644 index 0000000000..fe51488c70 --- /dev/null +++ b/test/validator/samples/await-shorthand-no-then/warnings.json @@ -0,0 +1 @@ +[] From 1c14e6e97111117da5a56b0651cd2482e37d7043 Mon Sep 17 00:00:00 2001 From: Conduitry Date: Wed, 22 Apr 2020 06:40:12 -0400 Subject: [PATCH 04/69] add Node 14 to CI matrix --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 14824ecdfa..457ddb3bab 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,7 +5,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - node-version: [8, 10, 12] + node-version: [8, 10, 12, 14] os: [ubuntu-latest, windows-latest, macOS-latest] steps: - run: git config --global core.autocrlf false From 51c6aaff0b9dcf32f5e550f49e180c7bd201a0c3 Mon Sep 17 00:00:00 2001 From: pushkin Date: Thu, 23 Apr 2020 15:04:51 +0200 Subject: [PATCH 05/69] fix parent in slot wrapper (#4705) --- src/compiler/compile/render_dom/wrappers/Slot.ts | 2 +- .../samples/slot-if-block-update-no-anchor/_config.js | 7 +++++++ .../samples/slot-if-block-update-no-anchor/main.svelte | 9 +++++++++ 3 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 test/runtime/samples/slot-if-block-update-no-anchor/_config.js create mode 100644 test/runtime/samples/slot-if-block-update-no-anchor/main.svelte diff --git a/src/compiler/compile/render_dom/wrappers/Slot.ts b/src/compiler/compile/render_dom/wrappers/Slot.ts index 1111a7cffe..492136303a 100644 --- a/src/compiler/compile/render_dom/wrappers/Slot.ts +++ b/src/compiler/compile/render_dom/wrappers/Slot.ts @@ -45,7 +45,7 @@ export default class SlotWrapper extends Wrapper { renderer, this.fallback, node.children, - parent, + this, strip_whitespace, next_sibling ); diff --git a/test/runtime/samples/slot-if-block-update-no-anchor/_config.js b/test/runtime/samples/slot-if-block-update-no-anchor/_config.js new file mode 100644 index 0000000000..060cbaa619 --- /dev/null +++ b/test/runtime/samples/slot-if-block-update-no-anchor/_config.js @@ -0,0 +1,7 @@ +export default { + test({ assert, target, component }) { + assert.htmlEqual(target.innerHTML, ``); + component.enabled = true; + assert.htmlEqual(target.innerHTML, `enabled`); + }, +}; diff --git a/test/runtime/samples/slot-if-block-update-no-anchor/main.svelte b/test/runtime/samples/slot-if-block-update-no-anchor/main.svelte new file mode 100644 index 0000000000..7960f35bc9 --- /dev/null +++ b/test/runtime/samples/slot-if-block-update-no-anchor/main.svelte @@ -0,0 +1,9 @@ + + + + + {#if enabled}enabled{/if} + + From f11b2052cc86dc2d8acd15b2cefe49ed36d0a552 Mon Sep 17 00:00:00 2001 From: Conduitry Date: Thu, 23 Apr 2020 09:06:17 -0400 Subject: [PATCH 06/69] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ca47ad51b7..356f73089d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ * Try using `globalThis` rather than `globals` for the benefit of non-Node servers and web workers ([#3561](https://github.com/sveltejs/svelte/issues/3561), [#4545](https://github.com/sveltejs/svelte/issues/4545)) * Support `{#await ... catch ...}` syntax shorthand ([#3623](https://github.com/sveltejs/svelte/issues/3623)) * Fix attaching of JS debugging comments to HTML comments ([#4565](https://github.com/sveltejs/svelte/issues/4565)) +* Fix `{#if}` block directly within `` ([#4703](https://github.com/sveltejs/svelte/issues/4703)) ## 3.20.1 From 467fc84d7de1fa3a45a8b65082fdb6c1ff61a396 Mon Sep 17 00:00:00 2001 From: pushkin Date: Thu, 23 Apr 2020 15:08:42 +0200 Subject: [PATCH 07/69] docs: mention |self modifier in reference section (#4709) --- site/content/docs/02-template-syntax.md | 1 + 1 file changed, 1 insertion(+) diff --git a/site/content/docs/02-template-syntax.md b/site/content/docs/02-template-syntax.md index 04764d0601..2f99c0db82 100644 --- a/site/content/docs/02-template-syntax.md +++ b/site/content/docs/02-template-syntax.md @@ -462,6 +462,7 @@ The following modifiers are available: * `passive` — improves scrolling performance on touch/wheel events (Svelte will add it automatically where it's safe to do so) * `capture` — fires the handler during the *capture* phase instead of the *bubbling* phase * `once` — remove the handler after the first time it runs +* `self` — only trigger handler if event.target is the element itself Modifiers can be chained together, e.g. `on:click|once|capture={...}`. From 7ac3e6021a1f04202f52859835ff4a3ec4fb98b7 Mon Sep 17 00:00:00 2001 From: pushkin Date: Thu, 23 Apr 2020 15:58:03 +0200 Subject: [PATCH 08/69] use value check instead of guard for number inputs (#4689) --- .../render_dom/wrappers/Element/Binding.ts | 24 ++++++++++---- .../samples/binding-input-number-2/_config.js | 31 +++++++++++++++++++ .../binding-input-number-2/main.svelte | 5 +++ 3 files changed, 54 insertions(+), 6 deletions(-) create mode 100644 test/runtime/samples/binding-input-number-2/_config.js create mode 100644 test/runtime/samples/binding-input-number-2/main.svelte diff --git a/src/compiler/compile/render_dom/wrappers/Element/Binding.ts b/src/compiler/compile/render_dom/wrappers/Element/Binding.ts index eca6e9d325..ac5732eae7 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/Binding.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/Binding.ts @@ -57,7 +57,7 @@ export default class BindingWrapper { this.is_readonly = this.node.is_readonly; - this.needs_lock = this.node.name === 'currentTime' || (parent.node.name === 'input' && parent.node.get_static_attribute_value('type') === 'number'); // TODO others? + this.needs_lock = this.node.name === 'currentTime'; // TODO others? } get_dependencies() { @@ -93,11 +93,23 @@ export default class BindingWrapper { update_conditions.push(block.renderer.dirty(dependency_array)); } - if (parent.node.name === 'input') { - const type = parent.node.get_static_attribute_value('type'); - - if (type === null || type === "" || type === "text" || type === "email" || type === "password") { - update_conditions.push(x`${parent.var}.${this.node.name} !== ${this.snippet}`); + if (parent.node.name === "input") { + const type = parent.node.get_static_attribute_value("type"); + + if ( + type === null || + type === "" || + type === "text" || + type === "email" || + type === "password" + ) { + update_conditions.push( + x`${parent.var}.${this.node.name} !== ${this.snippet}` + ); + } else if (type === "number") { + update_conditions.push( + x`@to_number(${parent.var}.${this.node.name}) !== ${this.snippet}` + ); } } diff --git a/test/runtime/samples/binding-input-number-2/_config.js b/test/runtime/samples/binding-input-number-2/_config.js new file mode 100644 index 0000000000..a3510eb757 --- /dev/null +++ b/test/runtime/samples/binding-input-number-2/_config.js @@ -0,0 +1,31 @@ +export default { + test({ assert, target, window, component }) { + const input = target.querySelector("input"); + const inputEvent = new window.InputEvent("input"); + assert.equal(component.value, 5); + assert.equal(input.value, "5"); + + input.value = "5."; + input.dispatchEvent(inputEvent); + + // input type number has value === "" if ends with dot/comma + assert.equal(component.value, undefined); + assert.equal(input.value, ""); + + input.value = "5.5"; + input.dispatchEvent(inputEvent); + + assert.equal(component.value, 5.5); + assert.equal(input.value, "5.5"); + + input.value = "5.50"; + input.dispatchEvent(inputEvent); + + assert.equal(component.value, 5.5); + assert.equal(input.value, "5.50"); + + component.value = 1; + assert.equal(component.value, 1); + assert.equal(input.value, "1"); + }, +}; diff --git a/test/runtime/samples/binding-input-number-2/main.svelte b/test/runtime/samples/binding-input-number-2/main.svelte new file mode 100644 index 0000000000..62119547a0 --- /dev/null +++ b/test/runtime/samples/binding-input-number-2/main.svelte @@ -0,0 +1,5 @@ + + + From 68ac96b8b87ad7f7c69299b74696475b2a4e2417 Mon Sep 17 00:00:00 2001 From: Conduitry Date: Thu, 23 Apr 2020 10:04:24 -0400 Subject: [PATCH 09/69] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 356f73089d..1bd390fd7f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ * Try using `globalThis` rather than `globals` for the benefit of non-Node servers and web workers ([#3561](https://github.com/sveltejs/svelte/issues/3561), [#4545](https://github.com/sveltejs/svelte/issues/4545)) * Support `{#await ... catch ...}` syntax shorthand ([#3623](https://github.com/sveltejs/svelte/issues/3623)) * Fix attaching of JS debugging comments to HTML comments ([#4565](https://github.com/sveltejs/svelte/issues/4565)) +* Fix issues with `` updates ([#4631](https://github.com/sveltejs/svelte/issues/4631), [#4687](https://github.com/sveltejs/svelte/issues/4687)) * Fix `{#if}` block directly within `` ([#4703](https://github.com/sveltejs/svelte/issues/4703)) ## 3.20.1 From ddfb751fc9497707719a2402fc21d3ff899a09f1 Mon Sep 17 00:00:00 2001 From: Daniel Imfeld Date: Thu, 23 Apr 2020 04:18:03 -1000 Subject: [PATCH 10/69] prevent illegal attribute names at compile time (#4650) --- src/compiler/compile/nodes/Element.ts | 10 +++++++++- .../samples/attribute-invalid-name-2/errors.json | 15 +++++++++++++++ .../samples/attribute-invalid-name-2/input.svelte | 1 + .../samples/attribute-invalid-name-3/errors.json | 15 +++++++++++++++ .../samples/attribute-invalid-name-3/input.svelte | 1 + .../samples/attribute-invalid-name-4/errors.json | 15 +++++++++++++++ .../samples/attribute-invalid-name-4/input.svelte | 1 + .../samples/attribute-invalid-name-5/errors.json | 15 +++++++++++++++ .../samples/attribute-invalid-name-5/input.svelte | 1 + .../samples/attribute-invalid-name/errors.json | 15 +++++++++++++++ .../samples/attribute-invalid-name/input.svelte | 1 + 11 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 test/validator/samples/attribute-invalid-name-2/errors.json create mode 100644 test/validator/samples/attribute-invalid-name-2/input.svelte create mode 100644 test/validator/samples/attribute-invalid-name-3/errors.json create mode 100644 test/validator/samples/attribute-invalid-name-3/input.svelte create mode 100644 test/validator/samples/attribute-invalid-name-4/errors.json create mode 100644 test/validator/samples/attribute-invalid-name-4/input.svelte create mode 100644 test/validator/samples/attribute-invalid-name-5/errors.json create mode 100644 test/validator/samples/attribute-invalid-name-5/input.svelte create mode 100644 test/validator/samples/attribute-invalid-name/errors.json create mode 100644 test/validator/samples/attribute-invalid-name/input.svelte diff --git a/src/compiler/compile/nodes/Element.ts b/src/compiler/compile/nodes/Element.ts index 8c9c0bfe18..8ff36de31a 100644 --- a/src/compiler/compile/nodes/Element.ts +++ b/src/compiler/compile/nodes/Element.ts @@ -378,6 +378,14 @@ export default class Element extends Node { } } + + if (/(^[0-9-.])|[\^$@%&#?!|()[\]{}^*+~;]/.test(name)) { + component.error(attribute, { + code: `illegal-attribute`, + message: `'${name}' is not a valid attribute name`, + }); + } + if (name === 'slot') { if (!attribute.is_static) { component.error(attribute, { @@ -768,4 +776,4 @@ function within_custom_element(parent: INode) { parent = parent.parent; } return false; -} \ No newline at end of file +} diff --git a/test/validator/samples/attribute-invalid-name-2/errors.json b/test/validator/samples/attribute-invalid-name-2/errors.json new file mode 100644 index 0000000000..9e0c437339 --- /dev/null +++ b/test/validator/samples/attribute-invalid-name-2/errors.json @@ -0,0 +1,15 @@ +[{ + "code": "illegal-attribute", + "message": "'3aa' is not a valid attribute name", + "start": { + "line": 1, + "column": 3, + "character": 3 + }, + "end": { + "line": 1, + "column": 12, + "character": 12 + }, + "pos": 3 +}] diff --git a/test/validator/samples/attribute-invalid-name-2/input.svelte b/test/validator/samples/attribute-invalid-name-2/input.svelte new file mode 100644 index 0000000000..6958a1b5aa --- /dev/null +++ b/test/validator/samples/attribute-invalid-name-2/input.svelte @@ -0,0 +1 @@ +

Test

diff --git a/test/validator/samples/attribute-invalid-name-3/errors.json b/test/validator/samples/attribute-invalid-name-3/errors.json new file mode 100644 index 0000000000..f583226882 --- /dev/null +++ b/test/validator/samples/attribute-invalid-name-3/errors.json @@ -0,0 +1,15 @@ +[{ + "code": "illegal-attribute", + "message": "'a*a' is not a valid attribute name", + "start": { + "line": 1, + "column": 3, + "character": 3 + }, + "end": { + "line": 1, + "column": 6, + "character": 6 + }, + "pos": 3 +}] diff --git a/test/validator/samples/attribute-invalid-name-3/input.svelte b/test/validator/samples/attribute-invalid-name-3/input.svelte new file mode 100644 index 0000000000..87c2df979a --- /dev/null +++ b/test/validator/samples/attribute-invalid-name-3/input.svelte @@ -0,0 +1 @@ +

Test

diff --git a/test/validator/samples/attribute-invalid-name-4/errors.json b/test/validator/samples/attribute-invalid-name-4/errors.json new file mode 100644 index 0000000000..a8959cebf3 --- /dev/null +++ b/test/validator/samples/attribute-invalid-name-4/errors.json @@ -0,0 +1,15 @@ +[{ + "code": "illegal-attribute", + "message": "'-a' is not a valid attribute name", + "start": { + "line": 1, + "column": 3, + "character": 3 + }, + "end": { + "line": 1, + "column": 5, + "character": 5 + }, + "pos": 3 +}] diff --git a/test/validator/samples/attribute-invalid-name-4/input.svelte b/test/validator/samples/attribute-invalid-name-4/input.svelte new file mode 100644 index 0000000000..92f68449b9 --- /dev/null +++ b/test/validator/samples/attribute-invalid-name-4/input.svelte @@ -0,0 +1 @@ +

Test

diff --git a/test/validator/samples/attribute-invalid-name-5/errors.json b/test/validator/samples/attribute-invalid-name-5/errors.json new file mode 100644 index 0000000000..b1e3acc5ef --- /dev/null +++ b/test/validator/samples/attribute-invalid-name-5/errors.json @@ -0,0 +1,15 @@ +[{ + "code": "illegal-attribute", + "message": "'a;' is not a valid attribute name", + "start": { + "line": 1, + "column": 3, + "character": 3 + }, + "end": { + "line": 1, + "column": 11, + "character": 11 + }, + "pos": 3 +}] diff --git a/test/validator/samples/attribute-invalid-name-5/input.svelte b/test/validator/samples/attribute-invalid-name-5/input.svelte new file mode 100644 index 0000000000..3a7dd05137 --- /dev/null +++ b/test/validator/samples/attribute-invalid-name-5/input.svelte @@ -0,0 +1 @@ +

Test

diff --git a/test/validator/samples/attribute-invalid-name/errors.json b/test/validator/samples/attribute-invalid-name/errors.json new file mode 100644 index 0000000000..9bdde1ee46 --- /dev/null +++ b/test/validator/samples/attribute-invalid-name/errors.json @@ -0,0 +1,15 @@ +[{ + "code": "illegal-attribute", + "message": "'}' is not a valid attribute name", + "start": { + "line": 1, + "column": 3, + "character": 3 + }, + "end": { + "line": 1, + "column": 4, + "character": 4 + }, + "pos": 3 +}] diff --git a/test/validator/samples/attribute-invalid-name/input.svelte b/test/validator/samples/attribute-invalid-name/input.svelte new file mode 100644 index 0000000000..56d9cfe23f --- /dev/null +++ b/test/validator/samples/attribute-invalid-name/input.svelte @@ -0,0 +1 @@ +

Test

From 7b74e84ca757acee507487fbfb23a480b0056e52 Mon Sep 17 00:00:00 2001 From: Conduitry Date: Thu, 23 Apr 2020 10:19:23 -0400 Subject: [PATCH 11/69] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1bd390fd7f..5e80b0d095 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ * Support `{#await ... catch ...}` syntax shorthand ([#3623](https://github.com/sveltejs/svelte/issues/3623)) * Fix attaching of JS debugging comments to HTML comments ([#4565](https://github.com/sveltejs/svelte/issues/4565)) * Fix issues with `` updates ([#4631](https://github.com/sveltejs/svelte/issues/4631), [#4687](https://github.com/sveltejs/svelte/issues/4687)) +* Prevent illegal attribute names ([#4648](https://github.com/sveltejs/svelte/issues/4648)) * Fix `{#if}` block directly within `` ([#4703](https://github.com/sveltejs/svelte/issues/4703)) ## 3.20.1 From 2bf8fc7e6b5eb1c4ff0f02617e61396d910927bc Mon Sep 17 00:00:00 2001 From: Tan Li Hau Date: Fri, 24 Apr 2020 07:35:39 +0800 Subject: [PATCH 12/69] fix slot fallback update parent (#4598) --- CHANGELOG.md | 1 + .../component-slot-fallback-5/IconA.svelte | 1 + .../component-slot-fallback-5/IconB.svelte | 1 + .../component-slot-fallback-5/Inner.svelte | 13 ++++++++ .../component-slot-fallback-5/_config.js | 31 +++++++++++++++++++ .../component-slot-fallback-5/main.svelte | 5 +++ 6 files changed, 52 insertions(+) create mode 100644 test/runtime/samples/component-slot-fallback-5/IconA.svelte create mode 100644 test/runtime/samples/component-slot-fallback-5/IconB.svelte create mode 100644 test/runtime/samples/component-slot-fallback-5/Inner.svelte create mode 100644 test/runtime/samples/component-slot-fallback-5/_config.js create mode 100644 test/runtime/samples/component-slot-fallback-5/main.svelte diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e80b0d095..b1bcd39e10 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ * Try using `globalThis` rather than `globals` for the benefit of non-Node servers and web workers ([#3561](https://github.com/sveltejs/svelte/issues/3561), [#4545](https://github.com/sveltejs/svelte/issues/4545)) * Support `{#await ... catch ...}` syntax shorthand ([#3623](https://github.com/sveltejs/svelte/issues/3623)) * Fix attaching of JS debugging comments to HTML comments ([#4565](https://github.com/sveltejs/svelte/issues/4565)) +* Fix `` within `` ([#4597](https://github.com/sveltejs/svelte/issues/4597)) * Fix issues with `` updates ([#4631](https://github.com/sveltejs/svelte/issues/4631), [#4687](https://github.com/sveltejs/svelte/issues/4687)) * Prevent illegal attribute names ([#4648](https://github.com/sveltejs/svelte/issues/4648)) * Fix `{#if}` block directly within `` ([#4703](https://github.com/sveltejs/svelte/issues/4703)) diff --git a/test/runtime/samples/component-slot-fallback-5/IconA.svelte b/test/runtime/samples/component-slot-fallback-5/IconA.svelte new file mode 100644 index 0000000000..8921b6c531 --- /dev/null +++ b/test/runtime/samples/component-slot-fallback-5/IconA.svelte @@ -0,0 +1 @@ +Icon A \ No newline at end of file diff --git a/test/runtime/samples/component-slot-fallback-5/IconB.svelte b/test/runtime/samples/component-slot-fallback-5/IconB.svelte new file mode 100644 index 0000000000..8a1a95ead3 --- /dev/null +++ b/test/runtime/samples/component-slot-fallback-5/IconB.svelte @@ -0,0 +1 @@ +Icon B \ No newline at end of file diff --git a/test/runtime/samples/component-slot-fallback-5/Inner.svelte b/test/runtime/samples/component-slot-fallback-5/Inner.svelte new file mode 100644 index 0000000000..bc4b224c23 --- /dev/null +++ b/test/runtime/samples/component-slot-fallback-5/Inner.svelte @@ -0,0 +1,13 @@ + + + +
+ + + +
diff --git a/test/runtime/samples/component-slot-fallback-5/_config.js b/test/runtime/samples/component-slot-fallback-5/_config.js new file mode 100644 index 0000000000..fa374f3519 --- /dev/null +++ b/test/runtime/samples/component-slot-fallback-5/_config.js @@ -0,0 +1,31 @@ +export default { + html: ` + +
Icon B
+ `, + + async test({ assert, target, window }) { + const btn = target.querySelector("button"); + const clickEvent = new window.MouseEvent("click"); + + await btn.dispatchEvent(clickEvent); + + assert.htmlEqual( + target.innerHTML, + ` + +
Icon A
+ ` + ); + + await btn.dispatchEvent(clickEvent); + + assert.htmlEqual( + target.innerHTML, + ` + +
Icon B
+ ` + ); + } +}; diff --git a/test/runtime/samples/component-slot-fallback-5/main.svelte b/test/runtime/samples/component-slot-fallback-5/main.svelte new file mode 100644 index 0000000000..206ce21def --- /dev/null +++ b/test/runtime/samples/component-slot-fallback-5/main.svelte @@ -0,0 +1,5 @@ + + + From 8d8db4e8369799c65056d163abf760fcae0739f6 Mon Sep 17 00:00:00 2001 From: pushkin Date: Fri, 24 Apr 2020 01:40:19 +0200 Subject: [PATCH 13/69] ensure if_block dependency is dirty to cancel outro (#4634) --- .../compile/render_dom/wrappers/IfBlock.ts | 36 ++++++++----------- test/js/samples/if-block-simple/expected.js | 6 ++-- test/js/samples/transition-local/expected.js | 8 +++-- .../transition-repeated-outro/expected.js | 8 +++-- .../use-elements-as-anchors/expected.js | 30 ++++++++-------- .../Component.svelte | 18 ++++++++++ .../_config.js | 9 +++++ .../main.svelte | 8 +++++ .../Component.svelte | 18 ++++++++++ .../_config.js | 7 ++++ .../main.svelte | 10 ++++++ 11 files changed, 113 insertions(+), 45 deletions(-) create mode 100644 test/runtime/samples/transition-js-if-outro-unrelated-component-binding-update/Component.svelte create mode 100644 test/runtime/samples/transition-js-if-outro-unrelated-component-binding-update/_config.js create mode 100644 test/runtime/samples/transition-js-if-outro-unrelated-component-binding-update/main.svelte create mode 100644 test/runtime/samples/transition-js-if-outro-unrelated-component-store-update/Component.svelte create mode 100644 test/runtime/samples/transition-js-if-outro-unrelated-component-store-update/_config.js create mode 100644 test/runtime/samples/transition-js-if-outro-unrelated-component-store-update/main.svelte diff --git a/src/compiler/compile/render_dom/wrappers/IfBlock.ts b/src/compiler/compile/render_dom/wrappers/IfBlock.ts index 207e1e349e..9b3e56635d 100644 --- a/src/compiler/compile/render_dom/wrappers/IfBlock.ts +++ b/src/compiler/compile/render_dom/wrappers/IfBlock.ts @@ -520,28 +520,22 @@ export default class IfBlockWrapper extends Wrapper { if (branch.dependencies.length > 0) { const update_mount_node = this.get_update_mount_node(anchor); - const enter = dynamic - ? b` - if (${name}) { - ${name}.p(#ctx, #dirty); - ${has_transitions && b`@transition_in(${name}, 1);`} - } else { - ${name} = ${branch.block.name}(#ctx); - ${name}.c(); - ${has_transitions && b`@transition_in(${name}, 1);`} - ${name}.m(${update_mount_node}, ${anchor}); - } - ` - : b` - if (!${name}) { - ${name} = ${branch.block.name}(#ctx); - ${name}.c(); - ${has_transitions && b`@transition_in(${name}, 1);`} - ${name}.m(${update_mount_node}, ${anchor}); - } else { - ${has_transitions && b`@transition_in(${name}, 1);`} + const enter = b` + if (${name}) { + ${dynamic && b`${name}.p(#ctx, #dirty);`} + ${ + has_transitions && + b`if (${block.renderer.dirty(branch.dependencies)}) { + @transition_in(${name}, 1); + }` } - `; + } else { + ${name} = ${branch.block.name}(#ctx); + ${name}.c(); + ${has_transitions && b`@transition_in(${name}, 1);`} + ${name}.m(${update_mount_node}, ${anchor}); + } + `; if (branch.snippet) { block.chunks.update.push(b`if (${block.renderer.dirty(branch.dependencies)}) ${branch.condition} = ${branch.snippet}`); diff --git a/test/js/samples/if-block-simple/expected.js b/test/js/samples/if-block-simple/expected.js index b9fad863e2..eb6c8e8949 100644 --- a/test/js/samples/if-block-simple/expected.js +++ b/test/js/samples/if-block-simple/expected.js @@ -42,12 +42,12 @@ function create_fragment(ctx) { }, p(ctx, [dirty]) { if (/*foo*/ ctx[0]) { - if (!if_block) { + if (if_block) { + + } else { if_block = create_if_block(ctx); if_block.c(); if_block.m(if_block_anchor.parentNode, if_block_anchor); - } else { - } } else if (if_block) { if_block.d(1); diff --git a/test/js/samples/transition-local/expected.js b/test/js/samples/transition-local/expected.js index a5d3b6318f..25a03f026f 100644 --- a/test/js/samples/transition-local/expected.js +++ b/test/js/samples/transition-local/expected.js @@ -28,13 +28,15 @@ function create_if_block(ctx) { }, p(ctx, dirty) { if (/*y*/ ctx[1]) { - if (!if_block) { + if (if_block) { + if (dirty & /*y*/ 2) { + transition_in(if_block, 1); + } + } else { if_block = create_if_block_1(ctx); if_block.c(); transition_in(if_block, 1); if_block.m(if_block_anchor.parentNode, if_block_anchor); - } else { - transition_in(if_block, 1); } } else if (if_block) { if_block.d(1); diff --git a/test/js/samples/transition-repeated-outro/expected.js b/test/js/samples/transition-repeated-outro/expected.js index 6f071328a4..1f76a93666 100644 --- a/test/js/samples/transition-repeated-outro/expected.js +++ b/test/js/samples/transition-repeated-outro/expected.js @@ -63,13 +63,15 @@ function create_fragment(ctx) { }, p(ctx, [dirty]) { if (/*num*/ ctx[0] < 5) { - if (!if_block) { + if (if_block) { + if (dirty & /*num*/ 1) { + transition_in(if_block, 1); + } + } else { if_block = create_if_block(ctx); if_block.c(); transition_in(if_block, 1); if_block.m(if_block_anchor.parentNode, if_block_anchor); - } else { - transition_in(if_block, 1); } } else if (if_block) { group_outros(); diff --git a/test/js/samples/use-elements-as-anchors/expected.js b/test/js/samples/use-elements-as-anchors/expected.js index 52635e9b78..5be8808edb 100644 --- a/test/js/samples/use-elements-as-anchors/expected.js +++ b/test/js/samples/use-elements-as-anchors/expected.js @@ -157,12 +157,12 @@ function create_fragment(ctx) { }, p(ctx, [dirty]) { if (/*a*/ ctx[0]) { - if (!if_block0) { + if (if_block0) { + + } else { if_block0 = create_if_block_4(ctx); if_block0.c(); if_block0.m(div, t0); - } else { - } } else if (if_block0) { if_block0.d(1); @@ -170,12 +170,12 @@ function create_fragment(ctx) { } if (/*b*/ ctx[1]) { - if (!if_block1) { + if (if_block1) { + + } else { if_block1 = create_if_block_3(ctx); if_block1.c(); if_block1.m(div, t3); - } else { - } } else if (if_block1) { if_block1.d(1); @@ -183,12 +183,12 @@ function create_fragment(ctx) { } if (/*c*/ ctx[2]) { - if (!if_block2) { + if (if_block2) { + + } else { if_block2 = create_if_block_2(ctx); if_block2.c(); if_block2.m(div, t4); - } else { - } } else if (if_block2) { if_block2.d(1); @@ -196,12 +196,12 @@ function create_fragment(ctx) { } if (/*d*/ ctx[3]) { - if (!if_block3) { + if (if_block3) { + + } else { if_block3 = create_if_block_1(ctx); if_block3.c(); if_block3.m(div, null); - } else { - } } else if (if_block3) { if_block3.d(1); @@ -209,12 +209,12 @@ function create_fragment(ctx) { } if (/*e*/ ctx[4]) { - if (!if_block4) { + if (if_block4) { + + } else { if_block4 = create_if_block(ctx); if_block4.c(); if_block4.m(if_block4_anchor.parentNode, if_block4_anchor); - } else { - } } else if (if_block4) { if_block4.d(1); diff --git a/test/runtime/samples/transition-js-if-outro-unrelated-component-binding-update/Component.svelte b/test/runtime/samples/transition-js-if-outro-unrelated-component-binding-update/Component.svelte new file mode 100644 index 0000000000..adb9aaea0e --- /dev/null +++ b/test/runtime/samples/transition-js-if-outro-unrelated-component-binding-update/Component.svelte @@ -0,0 +1,18 @@ + + + + `, + before_test() { + delete require.cache[path.resolve(__dirname, 'data.js')]; + }, + async test({ assert, target, window, }) { + const btn = target.querySelector('button'); + const clickEvent = new window.MouseEvent('click'); + + await btn.dispatchEvent(clickEvent); + + assert.htmlEqual(target.innerHTML, ` + import +

1 + 2 + 3 + 4 + 5 = 15

+ local +

1 + 2 + 3 + 4 + 5 = 15

+ + `); + + await btn.dispatchEvent(clickEvent); + + assert.htmlEqual(target.innerHTML, ` + import +

1 + 2 + 3 + 4 + 5 + 6 = 21

+ local +

1 + 2 + 3 + 4 + 5 + 6 = 21

+ + `); + } +}; diff --git a/test/runtime/samples/reactive-import-statement/data.js b/test/runtime/samples/reactive-import-statement/data.js new file mode 100644 index 0000000000..ad494c7cb0 --- /dev/null +++ b/test/runtime/samples/reactive-import-statement/data.js @@ -0,0 +1 @@ +export const numbers = [1, 2, 3, 4]; \ No newline at end of file diff --git a/test/runtime/samples/reactive-import-statement/main.svelte b/test/runtime/samples/reactive-import-statement/main.svelte new file mode 100644 index 0000000000..daded0494f --- /dev/null +++ b/test/runtime/samples/reactive-import-statement/main.svelte @@ -0,0 +1,19 @@ + + +import

{numbers.join(' + ')} = {sum}

+local

{local_numbers.join(' + ')} = {local_sum}

+ + \ No newline at end of file From e3fef0f740fa788bba68a29f5561ad439475edbb Mon Sep 17 00:00:00 2001 From: Stephen Demjanenko Date: Mon, 27 Apr 2020 06:15:28 -0700 Subject: [PATCH 21/69] site: fix scaling in script to get example thumbnails (#4727) --- site/scripts/get-example-thumbnails/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/scripts/get-example-thumbnails/index.js b/site/scripts/get-example-thumbnails/index.js index e5b977c6d4..770fca852f 100644 --- a/site/scripts/get-example-thumbnails/index.js +++ b/site/scripts/get-example-thumbnails/index.js @@ -45,7 +45,7 @@ async function main() { image.autocrop(); // image.scale(0.25); - if (image.bitmap.width > 200 || image.bitmap.width > 200) { + if (image.bitmap.width > 200 || image.bitmap.height > 200) { const scale = Math.min( 200 / image.bitmap.width, 200 / image.bitmap.height From cf72b4157575e4a64d73c75dc26fa22bcd25f719 Mon Sep 17 00:00:00 2001 From: nomnomnomnom Date: Tue, 28 Apr 2020 13:49:44 -0700 Subject: [PATCH 22/69] fix misaligned line numbers in sourcemaps (#4738) --- src/compiler/parse/read/script.ts | 3 ++- .../samples/script-after-comment/input.svelte | 9 +++++++++ .../samples/script-after-comment/test.js | 16 ++++++++++++++++ test/sourcemaps/samples/two-scripts/input.svelte | 9 +++++++++ test/sourcemaps/samples/two-scripts/test.js | 16 ++++++++++++++++ 5 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 test/sourcemaps/samples/script-after-comment/input.svelte create mode 100644 test/sourcemaps/samples/script-after-comment/test.js create mode 100644 test/sourcemaps/samples/two-scripts/input.svelte create mode 100644 test/sourcemaps/samples/two-scripts/test.js diff --git a/src/compiler/parse/read/script.ts b/src/compiler/parse/read/script.ts index 130c346ba1..8260894215 100644 --- a/src/compiler/parse/read/script.ts +++ b/src/compiler/parse/read/script.ts @@ -37,7 +37,8 @@ export default function read_script(parser: Parser, start: number, attributes: N message: ` + +{foo.bar.baz} diff --git a/test/sourcemaps/samples/script-after-comment/test.js b/test/sourcemaps/samples/script-after-comment/test.js new file mode 100644 index 0000000000..b473970112 --- /dev/null +++ b/test/sourcemaps/samples/script-after-comment/test.js @@ -0,0 +1,16 @@ +export function test({ assert, smc, locateInSource, locateInGenerated }) { + const expected = locateInSource( 'assertThisLine' ); + const start = locateInGenerated( 'assertThisLine' ); + + const actual = smc.originalPositionFor({ + line: start.line + 1, + column: start.column + }); + + assert.deepEqual( actual, { + source: 'input.svelte', + name: null, + line: expected.line + 1, + column: expected.column + }); +} diff --git a/test/sourcemaps/samples/two-scripts/input.svelte b/test/sourcemaps/samples/two-scripts/input.svelte new file mode 100644 index 0000000000..4c869827c0 --- /dev/null +++ b/test/sourcemaps/samples/two-scripts/input.svelte @@ -0,0 +1,9 @@ + + + + +{foo.bar.baz} diff --git a/test/sourcemaps/samples/two-scripts/test.js b/test/sourcemaps/samples/two-scripts/test.js new file mode 100644 index 0000000000..b473970112 --- /dev/null +++ b/test/sourcemaps/samples/two-scripts/test.js @@ -0,0 +1,16 @@ +export function test({ assert, smc, locateInSource, locateInGenerated }) { + const expected = locateInSource( 'assertThisLine' ); + const start = locateInGenerated( 'assertThisLine' ); + + const actual = smc.originalPositionFor({ + line: start.line + 1, + column: start.column + }); + + assert.deepEqual( actual, { + source: 'input.svelte', + name: null, + line: expected.line + 1, + column: expected.column + }); +} From 8c4e72647ae119401557a31df77a02568671f8d6 Mon Sep 17 00:00:00 2001 From: Conduitry Date: Tue, 28 Apr 2020 16:50:59 -0400 Subject: [PATCH 23/69] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3fceb5f2fc..660f834ac6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased +* Fix misaligned line numbers in source maps ([#3906](https://github.com/sveltejs/svelte/issues/3906)) * Fix reactivity with imported values that are then mutated ([#4555](https://github.com/sveltejs/svelte/issues/4555)) ## 3.21.0 From 56d1af493bb9bf71b2665e9c20228721ce79447b Mon Sep 17 00:00:00 2001 From: Kalan Date: Wed, 29 Apr 2020 06:03:31 +0900 Subject: [PATCH 24/69] a11y: warn about href="javascript:..." (#4733) --- src/compiler/compile/nodes/Element.ts | 2 +- .../samples/a11y-anchor-is-valid/input.svelte | 3 ++- .../samples/a11y-anchor-is-valid/warnings.json | 15 +++++++++++++++ 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/compiler/compile/nodes/Element.ts b/src/compiler/compile/nodes/Element.ts index 8ff36de31a..f64da919c6 100644 --- a/src/compiler/compile/nodes/Element.ts +++ b/src/compiler/compile/nodes/Element.ts @@ -428,7 +428,7 @@ export default class Element extends Node { if (attribute) { const value = attribute.get_static_value(); - if (value === '' || value === '#') { + if (value === '' || value === '#' || /^\W*javascript:/i.test(value)) { component.warn(attribute, { code: `a11y-invalid-attribute`, message: `A11y: '${value}' is not a valid ${attribute.name} attribute` diff --git a/test/validator/samples/a11y-anchor-is-valid/input.svelte b/test/validator/samples/a11y-anchor-is-valid/input.svelte index 7b14a80c9f..6d0f77a308 100644 --- a/test/validator/samples/a11y-anchor-is-valid/input.svelte +++ b/test/validator/samples/a11y-anchor-is-valid/input.svelte @@ -1,3 +1,4 @@ not actually a link invalid -invalid \ No newline at end of file +invalid +invalid \ No newline at end of file diff --git a/test/validator/samples/a11y-anchor-is-valid/warnings.json b/test/validator/samples/a11y-anchor-is-valid/warnings.json index 532c44ad24..9438a74f5b 100644 --- a/test/validator/samples/a11y-anchor-is-valid/warnings.json +++ b/test/validator/samples/a11y-anchor-is-valid/warnings.json @@ -43,5 +43,20 @@ "character": 61 }, "pos": 53 + }, + { + "code": "a11y-invalid-attribute", + "message": "A11y: 'javascript:void(0)' is not a valid href attribute", + "start": { + "line": 4, + "column": 3, + "character": 77 + }, + "end": { + "line": 4, + "column": 28, + "character": 102 + }, + "pos": 77 } ] From 628b09d8673c48f53283d339c17b778adfb60235 Mon Sep 17 00:00:00 2001 From: Conduitry Date: Tue, 28 Apr 2020 17:05:06 -0400 Subject: [PATCH 25/69] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 660f834ac6..459cf656c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * Fix misaligned line numbers in source maps ([#3906](https://github.com/sveltejs/svelte/issues/3906)) * Fix reactivity with imported values that are then mutated ([#4555](https://github.com/sveltejs/svelte/issues/4555)) +* Display a11y warning for `href="javascript:..."` ([#4733](https://github.com/sveltejs/svelte/pull/4733)) ## 3.21.0 From 75ae49312f5532360c29136462f808d5188a0482 Mon Sep 17 00:00:00 2001 From: Eddy Date: Wed, 29 Apr 2020 21:14:55 +0200 Subject: [PATCH 26/69] docs: fix link to transition section (#4748) --- site/content/docs/03-run-time.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/content/docs/03-run-time.md b/site/content/docs/03-run-time.md index 85661bd3b9..e393dfc1ab 100644 --- a/site/content/docs/03-run-time.md +++ b/site/content/docs/03-run-time.md @@ -521,7 +521,7 @@ $: $size = big ? 100 : 10; ### `svelte/transition` -The `svelte/transition` module exports six functions: `fade`, `fly`, `slide`, `scale`, `draw` and `crossfade`. They are for use with svelte [`transitions`](docs#Transitions). +The `svelte/transition` module exports six functions: `fade`, `fly`, `slide`, `scale`, `draw` and `crossfade`. They are for use with Svelte [`transitions`](docs#transition_fn). #### `fade` From 35a910eea21d73f871a10335f1059c9dcb154eb5 Mon Sep 17 00:00:00 2001 From: Conduitry Date: Wed, 29 Apr 2020 15:17:29 -0400 Subject: [PATCH 27/69] docs: fix link to animate section --- site/content/docs/03-run-time.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/content/docs/03-run-time.md b/site/content/docs/03-run-time.md index e393dfc1ab..fc21abb20d 100644 --- a/site/content/docs/03-run-time.md +++ b/site/content/docs/03-run-time.md @@ -758,7 +758,7 @@ The `speed` parameter is a means of setting the duration of the transition relat ### `svelte/animate` -The `svelte/animate` module exports one function for use with svelte [animations](docs#Animations). +The `svelte/animate` module exports one function for use with Svelte [animations](docs#animate_fn). #### `flip` From 0e61e51358039527bc153bf577ddc46feb082ce8 Mon Sep 17 00:00:00 2001 From: Antony Jones Date: Wed, 29 Apr 2020 20:38:58 +0100 Subject: [PATCH 28/69] docs: add v2 site link (#4745) --- site/content/docs/00-introduction.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/site/content/docs/00-introduction.md b/site/content/docs/00-introduction.md index 15fdffbde2..2ca82a8698 100644 --- a/site/content/docs/00-introduction.md +++ b/site/content/docs/00-introduction.md @@ -6,4 +6,6 @@ This page contains detailed API reference documentation. It's intended to be a r If that's not you (yet), you may prefer to visit the [interactive tutorial](tutorial) or the [examples](examples) before consulting this reference. -Don't be shy about asking for help in the [Discord chatroom](chat). \ No newline at end of file +Don't be shy about asking for help in the [Discord chatroom](chat). + +Using an older version of Svelte? Have a look at the [v2 docs](https://v2.svelte.dev). \ No newline at end of file From 4ac2ea3cef95c95c46141bbb615b38f585d941df Mon Sep 17 00:00:00 2001 From: Olubisi Idris Ayinde Date: Wed, 29 Apr 2020 20:39:55 +0100 Subject: [PATCH 29/69] contributing.md: add missing punctuation (#4746) --- CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f7df9770db..c3eff81da3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -93,7 +93,7 @@ Test samples are kept in `/test/xxx/samples` folder. #### Running tests -1. To run test, run `npm run test` +1. To run test, run `npm run test`. 1. To run test for a specific feature, you can use the `-g` (aka `--grep`) option. For example, to only run test involving transitions, run `npm run test -- -g transition`. ##### Running solo test @@ -130,7 +130,7 @@ The core Svelte team will be monitoring for pull requests. Do help us by making ### Code conventions -- `snake_case` for internal variable names and methods +- `snake_case` for internal variable names and methods. - `camelCase` for public variable names and methods. ## License From 07242f994c00970724525633ed318f8acbbd4d81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Lins?= Date: Wed, 29 Apr 2020 16:42:11 -0300 Subject: [PATCH 30/69] a11y: do not warn about / (#4739) --- src/compiler/compile/nodes/Element.ts | 31 ++++++++++++------- .../samples/a11y-anchor-is-valid/input.svelte | 6 +++- .../a11y-anchor-is-valid/warnings.json | 30 ++++++++++++++++++ 3 files changed, 54 insertions(+), 13 deletions(-) diff --git a/src/compiler/compile/nodes/Element.ts b/src/compiler/compile/nodes/Element.ts index f64da919c6..f7f3cc55bc 100644 --- a/src/compiler/compile/nodes/Element.ts +++ b/src/compiler/compile/nodes/Element.ts @@ -423,22 +423,29 @@ export default class Element extends Node { // handle special cases if (this.name === 'a') { - const attribute = attribute_map.get('href') || attribute_map.get('xlink:href'); - - if (attribute) { - const value = attribute.get_static_value(); - - if (value === '' || value === '#' || /^\W*javascript:/i.test(value)) { - component.warn(attribute, { + const href_attribute = attribute_map.get('href') || attribute_map.get('xlink:href'); + const id_attribute = attribute_map.get('id'); + const name_attribute = attribute_map.get('name'); + + if (href_attribute) { + const href_value = href_attribute.get_static_value(); + + if (href_value === '' || href_value === '#' || /^\W*javascript:/i.test(href_value)) { + component.warn(href_attribute, { code: `a11y-invalid-attribute`, - message: `A11y: '${value}' is not a valid ${attribute.name} attribute` + message: `A11y: '${href_value}' is not a valid ${href_attribute.name} attribute` }); } } else { - component.warn(this, { - code: `a11y-missing-attribute`, - message: `A11y: element should have an href attribute` - }); + const id_attribute_valid = id_attribute && id_attribute.get_static_value() !== ''; + const name_attribute_valid = name_attribute && name_attribute.get_static_value() !== ''; + + if (!id_attribute_valid && !name_attribute_valid) { + component.warn(this, { + code: `a11y-missing-attribute`, + message: `A11y: element should have an href attribute` + }); + } } } diff --git a/test/validator/samples/a11y-anchor-is-valid/input.svelte b/test/validator/samples/a11y-anchor-is-valid/input.svelte index 6d0f77a308..8933bd7560 100644 --- a/test/validator/samples/a11y-anchor-is-valid/input.svelte +++ b/test/validator/samples/a11y-anchor-is-valid/input.svelte @@ -1,4 +1,8 @@ not actually a link invalid invalid -invalid \ No newline at end of file +invalid +invalid +invalid +valid +valid \ No newline at end of file diff --git a/test/validator/samples/a11y-anchor-is-valid/warnings.json b/test/validator/samples/a11y-anchor-is-valid/warnings.json index 9438a74f5b..f04c6f1593 100644 --- a/test/validator/samples/a11y-anchor-is-valid/warnings.json +++ b/test/validator/samples/a11y-anchor-is-valid/warnings.json @@ -58,5 +58,35 @@ "character": 102 }, "pos": 77 + }, + { + "code": "a11y-missing-attribute", + "message": "A11y: element should have an href attribute", + "start": { + "line": 5, + "column": 0, + "character": 115 + }, + "end": { + "line": 5, + "column": 22, + "character": 137 + }, + "pos": 115 + }, + { + "code": "a11y-missing-attribute", + "message": "A11y: element should have an href attribute", + "start": { + "line": 6, + "column": 0, + "character": 138 + }, + "end": { + "line": 6, + "column": 20, + "character": 158 + }, + "pos": 138 } ] From 5604954f1514e92ea2b84907562d3c6193810224 Mon Sep 17 00:00:00 2001 From: Conduitry Date: Wed, 29 Apr 2020 15:43:37 -0400 Subject: [PATCH 31/69] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 459cf656c7..25f4e12f20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * Fix misaligned line numbers in source maps ([#3906](https://github.com/sveltejs/svelte/issues/3906)) * Fix reactivity with imported values that are then mutated ([#4555](https://github.com/sveltejs/svelte/issues/4555)) +* Do not display a11y warning about missing `href` for `` with `name` or `id` ([#4697](https://github.com/sveltejs/svelte/issues/4697)) * Display a11y warning for `href="javascript:..."` ([#4733](https://github.com/sveltejs/svelte/pull/4733)) ## 3.21.0 From edeeb05a6cd2130755cd47a7aa309c95f1124ce4 Mon Sep 17 00:00:00 2001 From: MichalBednarczyk <48881661+majkelbed@users.noreply.github.com> Date: Wed, 29 Apr 2020 21:48:45 +0200 Subject: [PATCH 32/69] docs: {#each} with key but without index (#4721) --- site/content/docs/02-template-syntax.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/site/content/docs/02-template-syntax.md b/site/content/docs/02-template-syntax.md index 3ee5b1dad1..165dd86106 100644 --- a/site/content/docs/02-template-syntax.md +++ b/site/content/docs/02-template-syntax.md @@ -206,6 +206,9 @@ Additional conditions can be added with `{:else if expression}`, optionally endi {#each expression as name, index}...{/each} ``` ```sv +{#each expression as name (key)}...{/each} +``` +```sv {#each expression as name, index (key)}...{/each} ``` ```sv @@ -242,6 +245,11 @@ An each block can also specify an *index*, equivalent to the second argument in If a *key* expression is provided — which must uniquely identify each list item — Svelte will use it to diff the list when data changes, rather than adding or removing items at the end. The key can be any object, but strings and numbers are recommended since they allow identity to persist when the objects themselves change. ```html +{#each items as item (item.id)} +
  • {item.name} x {item.qty}
  • +{/each} + + {#each items as item, i (item.id)}
  • {i + 1}: {item.name} x {item.qty}
  • {/each} From 3d811311b713a0ffd92d62012d7582f9d4cb6da0 Mon Sep 17 00:00:00 2001 From: Billy Levin Date: Wed, 29 Apr 2020 22:14:01 +0100 Subject: [PATCH 33/69] disable loop protection inside generators (#4716) --- src/compiler/compile/Component.ts | 16 +++++++++++++--- .../loop-protect-generator-opt-out/_config.js | 6 ++++++ .../loop-protect-generator-opt-out/main.svelte | 14 ++++++++++++++ 3 files changed, 33 insertions(+), 3 deletions(-) create mode 100644 test/runtime/samples/loop-protect-generator-opt-out/_config.js create mode 100644 test/runtime/samples/loop-protect-generator-opt-out/main.svelte diff --git a/src/compiler/compile/Component.ts b/src/compiler/compile/Component.ts index c83f8153a0..a5a31c8070 100644 --- a/src/compiler/compile/Component.ts +++ b/src/compiler/compile/Component.ts @@ -205,7 +205,7 @@ export default class Component { const variable = this.var_lookup.get(subscribable_name); if (variable) { - variable.referenced = true; + variable.referenced = true; variable.subscribable = true; } } else { @@ -714,8 +714,14 @@ export default class Component { }; let scope_updated = false; + let generator_count = 0; + walk(content, { enter(node: Node, parent, prop, index) { + if ((node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression') && node.generator === true) { + generator_count++; + } + if (map.has(node)) { scope = map.get(node); } @@ -742,8 +748,12 @@ export default class Component { }, leave(node: Node) { + if ((node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression') && node.generator === true) { + generator_count--; + } + // do it on leave, to prevent infinite loop - if (component.compile_options.dev && component.compile_options.loopGuardTimeout > 0) { + if (component.compile_options.dev && component.compile_options.loopGuardTimeout > 0 && generator_count <= 0) { const to_replace_for_loop_protect = component.loop_protect(node, scope, component.compile_options.loopGuardTimeout); if (to_replace_for_loop_protect) { this.replace(to_replace_for_loop_protect); @@ -1259,7 +1269,7 @@ export default class Component { const add_declaration = declaration => { if (this.reactive_declarations.includes(declaration)) return; - + declaration.dependencies.forEach(name => { if (declaration.assignees.has(name)) return; const earlier_declarations = lookup.get(name); diff --git a/test/runtime/samples/loop-protect-generator-opt-out/_config.js b/test/runtime/samples/loop-protect-generator-opt-out/_config.js new file mode 100644 index 0000000000..9b6a24b513 --- /dev/null +++ b/test/runtime/samples/loop-protect-generator-opt-out/_config.js @@ -0,0 +1,6 @@ +export default { + compileOptions: { + dev: true, + loopGuardTimeout: 1, + }, +}; diff --git a/test/runtime/samples/loop-protect-generator-opt-out/main.svelte b/test/runtime/samples/loop-protect-generator-opt-out/main.svelte new file mode 100644 index 0000000000..a7aa790681 --- /dev/null +++ b/test/runtime/samples/loop-protect-generator-opt-out/main.svelte @@ -0,0 +1,14 @@ + \ No newline at end of file From fe0a008a6a59f5377582ca85d1b97ea7e93087e2 Mon Sep 17 00:00:00 2001 From: Conduitry Date: Wed, 29 Apr 2020 17:15:35 -0400 Subject: [PATCH 34/69] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 25f4e12f20..78487a8d1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ * Fix misaligned line numbers in source maps ([#3906](https://github.com/sveltejs/svelte/issues/3906)) * Fix reactivity with imported values that are then mutated ([#4555](https://github.com/sveltejs/svelte/issues/4555)) * Do not display a11y warning about missing `href` for `
    ` with `name` or `id` ([#4697](https://github.com/sveltejs/svelte/issues/4697)) +* Disable infinite loop guard inside generators ([#4698](https://github.com/sveltejs/svelte/issues/4698)) * Display a11y warning for `href="javascript:..."` ([#4733](https://github.com/sveltejs/svelte/pull/4733)) ## 3.21.0 From b3364424d7d454640045f352314eec63b73bf040 Mon Sep 17 00:00:00 2001 From: JocelynLi Date: Thu, 30 Apr 2020 06:19:21 +0900 Subject: [PATCH 35/69] docs: tweak tutorial/default-values wording (#4600) --- site/content/tutorial/03-props/02-default-values/text.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/content/tutorial/03-props/02-default-values/text.md b/site/content/tutorial/03-props/02-default-values/text.md index 1532407380..17b486d0ad 100644 --- a/site/content/tutorial/03-props/02-default-values/text.md +++ b/site/content/tutorial/03-props/02-default-values/text.md @@ -2,7 +2,7 @@ title: Default values --- -We can easily specify default values for props: +We can easily specify default values for props in `Nested.svelte`: ```html - \ No newline at end of file + diff --git a/test/runtime/samples/window-event-custom/_config.js b/test/runtime/samples/window-event-custom/_config.js index 207fad1b0b..1c1075c0de 100644 --- a/test/runtime/samples/window-event-custom/_config.js +++ b/test/runtime/samples/window-event-custom/_config.js @@ -3,7 +3,7 @@ export default { async test({ assert, component, target, window }) { const event = new window.KeyboardEvent('keydown', { - which: 27 + key: 'Escape' }); await window.dispatchEvent(event); diff --git a/test/runtime/samples/window-event-custom/main.svelte b/test/runtime/samples/window-event-custom/main.svelte index ce453d9334..ff286f4309 100644 --- a/test/runtime/samples/window-event-custom/main.svelte +++ b/test/runtime/samples/window-event-custom/main.svelte @@ -3,7 +3,7 @@ function esc(node, callback) { function onKeyDown(event) { - if (event.which === 27) callback(event); + if (event.key === 'Escape') callback(event); } node.addEventListener('keydown', onKeyDown); return { @@ -16,4 +16,4 @@ -

    escaped: {escaped}

    \ No newline at end of file +

    escaped: {escaped}

    From f111cf6881d6708f0e7d0b7db9653f411dd6bab1 Mon Sep 17 00:00:00 2001 From: Conduitry Date: Sun, 3 May 2020 11:48:24 -0400 Subject: [PATCH 37/69] deconflict `anchor` variable name (#4769) --- CHANGELOG.md | 1 + src/compiler/compile/render_dom/Block.ts | 6 +++--- src/compiler/compile/render_dom/wrappers/AwaitBlock.ts | 2 +- src/compiler/compile/render_dom/wrappers/EachBlock.ts | 2 +- src/compiler/compile/render_dom/wrappers/Element/index.ts | 2 +- src/compiler/compile/render_dom/wrappers/IfBlock.ts | 6 +++--- .../compile/render_dom/wrappers/InlineComponent/index.ts | 4 ++-- src/compiler/compile/render_dom/wrappers/RawMustacheTag.ts | 2 +- src/compiler/compile/render_dom/wrappers/Slot.ts | 2 +- test/runtime/samples/deconflict-anchor/Anchor.svelte | 1 + test/runtime/samples/deconflict-anchor/_config.js | 4 ++++ test/runtime/samples/deconflict-anchor/main.svelte | 5 +++++ 12 files changed, 24 insertions(+), 13 deletions(-) create mode 100644 test/runtime/samples/deconflict-anchor/Anchor.svelte create mode 100644 test/runtime/samples/deconflict-anchor/_config.js create mode 100644 test/runtime/samples/deconflict-anchor/main.svelte diff --git a/CHANGELOG.md b/CHANGELOG.md index 78487a8d1b..2f977c041f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ * Do not display a11y warning about missing `href` for `
    ` with `name` or `id` ([#4697](https://github.com/sveltejs/svelte/issues/4697)) * Disable infinite loop guard inside generators ([#4698](https://github.com/sveltejs/svelte/issues/4698)) * Display a11y warning for `href="javascript:..."` ([#4733](https://github.com/sveltejs/svelte/pull/4733)) +* Fix variable name conflict with component called `` ([#4768](https://github.com/sveltejs/svelte/issues/4768)) ## 3.21.0 diff --git a/src/compiler/compile/render_dom/Block.ts b/src/compiler/compile/render_dom/Block.ts index f5c4281710..b29b4d4afd 100644 --- a/src/compiler/compile/render_dom/Block.ts +++ b/src/compiler/compile/render_dom/Block.ts @@ -185,7 +185,7 @@ export default class Block { this.chunks.mount.push(b`@append(${parent_node}, ${id});`); if (is_head(parent_node) && !no_detach) this.chunks.destroy.push(b`@detach(${id});`); } else { - this.chunks.mount.push(b`@insert(#target, ${id}, anchor);`); + this.chunks.mount.push(b`@insert(#target, ${id}, #anchor);`); if (!no_detach) this.chunks.destroy.push(b`if (detaching) @detach(${id});`); } } @@ -295,11 +295,11 @@ export default class Block { if (this.chunks.mount.length === 0) { properties.mount = noop; } else if (this.event_listeners.length === 0) { - properties.mount = x`function #mount(#target, anchor) { + properties.mount = x`function #mount(#target, #anchor) { ${this.chunks.mount} }`; } else { - properties.mount = x`function #mount(#target, anchor, #remount) { + properties.mount = x`function #mount(#target, #anchor, #remount) { ${this.chunks.mount} }`; } diff --git a/src/compiler/compile/render_dom/wrappers/AwaitBlock.ts b/src/compiler/compile/render_dom/wrappers/AwaitBlock.ts index d6ac17f0ce..1898a840d2 100644 --- a/src/compiler/compile/render_dom/wrappers/AwaitBlock.ts +++ b/src/compiler/compile/render_dom/wrappers/AwaitBlock.ts @@ -200,7 +200,7 @@ export default class AwaitBlockWrapper extends Wrapper { } const initial_mount_node = parent_node || '#target'; - const anchor_node = parent_node ? 'null' : 'anchor'; + const anchor_node = parent_node ? 'null' : '#anchor'; const has_transitions = this.pending.block.has_intro_method || this.pending.block.has_outro_method; diff --git a/src/compiler/compile/render_dom/wrappers/EachBlock.ts b/src/compiler/compile/render_dom/wrappers/EachBlock.ts index 43a0f754f9..1efadfb90c 100644 --- a/src/compiler/compile/render_dom/wrappers/EachBlock.ts +++ b/src/compiler/compile/render_dom/wrappers/EachBlock.ts @@ -219,7 +219,7 @@ export default class EachBlockWrapper extends Wrapper { } `); - const initial_anchor_node: Identifier = { type: 'Identifier', name: parent_node ? 'null' : 'anchor' }; + const initial_anchor_node: Identifier = { type: 'Identifier', name: parent_node ? 'null' : '#anchor' }; const initial_mount_node: Identifier = parent_node || { type: 'Identifier', name: '#target' }; const update_anchor_node = needs_anchor ? block.get_unique_name(`${this.var.name}_anchor`) diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index 64d02191fb..ab802b023d 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -320,7 +320,7 @@ export default class ElementWrapper extends Wrapper { block.chunks.destroy.push(b`@detach(${node});`); } } else { - block.chunks.mount.push(b`@insert(#target, ${node}, anchor);`); + block.chunks.mount.push(b`@insert(#target, ${node}, #anchor);`); // TODO we eventually need to consider what happens to elements // that belong to the same outgroup as an outroing element... diff --git a/src/compiler/compile/render_dom/wrappers/IfBlock.ts b/src/compiler/compile/render_dom/wrappers/IfBlock.ts index 78b821170f..e18d9f3b6b 100644 --- a/src/compiler/compile/render_dom/wrappers/IfBlock.ts +++ b/src/compiler/compile/render_dom/wrappers/IfBlock.ts @@ -293,7 +293,7 @@ export default class IfBlockWrapper extends Wrapper { `); const initial_mount_node = parent_node || '#target'; - const anchor_node = parent_node ? 'null' : 'anchor'; + const anchor_node = parent_node ? 'null' : '#anchor'; if (if_exists_condition) { block.chunks.mount.push( @@ -423,7 +423,7 @@ export default class IfBlockWrapper extends Wrapper { } const initial_mount_node = parent_node || '#target'; - const anchor_node = parent_node ? 'null' : 'anchor'; + const anchor_node = parent_node ? 'null' : '#anchor'; block.chunks.mount.push( if_current_block_type_index( @@ -519,7 +519,7 @@ export default class IfBlockWrapper extends Wrapper { `); const initial_mount_node = parent_node || '#target'; - const anchor_node = parent_node ? 'null' : 'anchor'; + const anchor_node = parent_node ? 'null' : '#anchor'; block.chunks.mount.push( b`if (${name}) ${name}.m(${initial_mount_node}, ${anchor_node});` diff --git a/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts b/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts index 4b1e787cbe..00f803bbbd 100644 --- a/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts +++ b/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts @@ -435,7 +435,7 @@ export default class InlineComponentWrapper extends Wrapper { block.chunks.mount.push(b` if (${name}) { - @mount_component(${name}, ${parent_node || '#target'}, ${parent_node ? 'null' : 'anchor'}); + @mount_component(${name}, ${parent_node || '#target'}, ${parent_node ? 'null' : '#anchor'}); } `); @@ -509,7 +509,7 @@ export default class InlineComponentWrapper extends Wrapper { } block.chunks.mount.push( - b`@mount_component(${name}, ${parent_node || '#target'}, ${parent_node ? 'null' : 'anchor'});` + b`@mount_component(${name}, ${parent_node || '#target'}, ${parent_node ? 'null' : '#anchor'});` ); block.chunks.intro.push(b` diff --git a/src/compiler/compile/render_dom/wrappers/RawMustacheTag.ts b/src/compiler/compile/render_dom/wrappers/RawMustacheTag.ts index 3b13e6c68a..63889dd829 100644 --- a/src/compiler/compile/render_dom/wrappers/RawMustacheTag.ts +++ b/src/compiler/compile/render_dom/wrappers/RawMustacheTag.ts @@ -54,7 +54,7 @@ export default class RawMustacheTagWrapper extends Tag { const update_anchor = in_head ? 'null' : needs_anchor ? html_anchor : this.next ? this.next.var : 'null'; block.chunks.hydrate.push(b`${html_tag} = new @HtmlTag(${init}, ${update_anchor});`); - block.chunks.mount.push(b`${html_tag}.m(${parent_node || '#target'}, ${parent_node ? null : 'anchor'});`); + block.chunks.mount.push(b`${html_tag}.m(${parent_node || '#target'}, ${parent_node ? null : '#anchor'});`); if (needs_anchor) { block.add_element(html_anchor, x`@empty()`, x`@empty()`, parent_node); diff --git a/src/compiler/compile/render_dom/wrappers/Slot.ts b/src/compiler/compile/render_dom/wrappers/Slot.ts index 492136303a..55ed031381 100644 --- a/src/compiler/compile/render_dom/wrappers/Slot.ts +++ b/src/compiler/compile/render_dom/wrappers/Slot.ts @@ -145,7 +145,7 @@ export default class SlotWrapper extends Wrapper { block.chunks.mount.push(b` if (${slot_or_fallback}) { - ${slot_or_fallback}.m(${parent_node || '#target'}, ${parent_node ? 'null' : 'anchor'}); + ${slot_or_fallback}.m(${parent_node || '#target'}, ${parent_node ? 'null' : '#anchor'}); } `); diff --git a/test/runtime/samples/deconflict-anchor/Anchor.svelte b/test/runtime/samples/deconflict-anchor/Anchor.svelte new file mode 100644 index 0000000000..e758d60056 --- /dev/null +++ b/test/runtime/samples/deconflict-anchor/Anchor.svelte @@ -0,0 +1 @@ +

    Anchor

    diff --git a/test/runtime/samples/deconflict-anchor/_config.js b/test/runtime/samples/deconflict-anchor/_config.js new file mode 100644 index 0000000000..817dd42fcb --- /dev/null +++ b/test/runtime/samples/deconflict-anchor/_config.js @@ -0,0 +1,4 @@ +export default { + preserveIdentifiers: true, + html: `

    Anchor

    ` +}; diff --git a/test/runtime/samples/deconflict-anchor/main.svelte b/test/runtime/samples/deconflict-anchor/main.svelte new file mode 100644 index 0000000000..cce6281e49 --- /dev/null +++ b/test/runtime/samples/deconflict-anchor/main.svelte @@ -0,0 +1,5 @@ + + + From a08a94ac33c9f15025bd571bd8f0fbe54a005b48 Mon Sep 17 00:00:00 2001 From: Pat Cavit Date: Sun, 3 May 2020 08:52:32 -0700 Subject: [PATCH 38/69] make setting tweened with duration=0 instantly update (#4766) --- src/runtime/motion/tweened.ts | 5 +++++ test/motion/index.js | 7 +++++++ 2 files changed, 12 insertions(+) diff --git a/src/runtime/motion/tweened.ts b/src/runtime/motion/tweened.ts index e33f0f79f9..abbb3b1aa2 100644 --- a/src/runtime/motion/tweened.ts +++ b/src/runtime/motion/tweened.ts @@ -93,6 +93,11 @@ export function tweened(value?: T, defaults: Options = {}): Tweened { interpolate = get_interpolator } = assign(assign({}, defaults), opts); + if (duration === 0) { + store.set(target_value); + return Promise.resolve(); + } + const start = now() + delay; let fn; diff --git a/test/motion/index.js b/test/motion/index.js index 33489b18e1..ec58c6df36 100644 --- a/test/motion/index.js +++ b/test/motion/index.js @@ -19,5 +19,12 @@ describe('motion', () => { size.set(100); assert.equal(get(size), 100); }); + + it('sets immediately when duration is 0', () => { + const size = tweened(0); + + size.set(100, { duration : 0 }); + assert.equal(get(size), 100); + }); }); }); From 09115b5256cbe88ae27879f5eac96f11d2366ebb Mon Sep 17 00:00:00 2001 From: Conduitry Date: Sun, 3 May 2020 11:53:47 -0400 Subject: [PATCH 39/69] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f977c041f..d3f99af139 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased * Fix misaligned line numbers in source maps ([#3906](https://github.com/sveltejs/svelte/issues/3906)) +* Make setting a `tweened` store using `duration: 0` instantly update the value ([#4399](https://github.com/sveltejs/svelte/issues/4399)) * Fix reactivity with imported values that are then mutated ([#4555](https://github.com/sveltejs/svelte/issues/4555)) * Do not display a11y warning about missing `href` for `
    ` with `name` or `id` ([#4697](https://github.com/sveltejs/svelte/issues/4697)) * Disable infinite loop guard inside generators ([#4698](https://github.com/sveltejs/svelte/issues/4698)) From 153b128fe28a3283b684fccb051deb66152a6d46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Lins?= Date: Sun, 3 May 2020 13:06:10 -0300 Subject: [PATCH 40/69] a11y: implement img-redundant-alt (#4750) --- src/compiler/compile/nodes/Element.ts | 49 ++++++++++++++----- .../input.svelte | 2 +- .../a11y-figcaption-right-place/input.svelte | 2 +- .../a11y-figcaption-wrong-place/input.svelte | 4 +- .../a11y-figcaption-wrong-place/warnings.json | 12 ++--- .../a11y-img-redundant-alt/input.svelte | 7 +++ .../a11y-img-redundant-alt/warnings.json | 47 ++++++++++++++++++ 7 files changed, 101 insertions(+), 22 deletions(-) create mode 100644 test/validator/samples/a11y-img-redundant-alt/input.svelte create mode 100644 test/validator/samples/a11y-img-redundant-alt/warnings.json diff --git a/src/compiler/compile/nodes/Element.ts b/src/compiler/compile/nodes/Element.ts index f7f3cc55bc..93997ae66e 100644 --- a/src/compiler/compile/nodes/Element.ts +++ b/src/compiler/compile/nodes/Element.ts @@ -272,6 +272,7 @@ export default class Element extends Node { } this.validate_attributes(); + this.validate_special_cases(); this.validate_bindings(); this.validate_content(); this.validate_event_handlers(); @@ -420,8 +421,16 @@ export default class Element extends Node { attribute_map.set(attribute.name, attribute); }); + } + + validate_special_cases() { + const { component,attributes } = this; + const attribute_map = new Map(); + + attributes.forEach(attribute => ( + attribute_map.set(attribute.name, attribute) + )); - // handle special cases if (this.name === 'a') { const href_attribute = attribute_map.get('href') || attribute_map.get('xlink:href'); const id_attribute = attribute_map.get('id'); @@ -447,9 +456,7 @@ export default class Element extends Node { }); } } - } - - else { + } else { const required_attributes = a11y_required_attributes[this.name]; if (required_attributes) { const has_attribute = required_attributes.some(name => attribute_map.has(name)); @@ -458,16 +465,34 @@ export default class Element extends Node { should_have_attribute(this, required_attributes); } } + } - if (this.name === 'input') { - const type = attribute_map.get('type'); - if (type && type.get_static_value() === 'image') { - const required_attributes = ['alt', 'aria-label', 'aria-labelledby']; - const has_attribute = required_attributes.some(name => attribute_map.has(name)); + if (this.name === 'input') { + const type = attribute_map.get('type'); + if (type && type.get_static_value() === 'image') { + const required_attributes = ['alt', 'aria-label', 'aria-labelledby']; + const has_attribute = required_attributes.some(name => attribute_map.has(name)); - if (!has_attribute) { - should_have_attribute(this, required_attributes, 'input type="image"'); - } + if (!has_attribute) { + should_have_attribute(this, required_attributes, 'input type="image"'); + } + } + } + + if (this.name === 'img') { + const alt_attribute = attribute_map.get('alt'); + const aria_hidden_attribute = attribute_map.get('aria-hidden'); + + const aria_hidden_exist = aria_hidden_attribute && aria_hidden_attribute.get_static_value(); + + if (alt_attribute && !aria_hidden_exist) { + const alt_value = alt_attribute.get_static_value(); + + if (alt_value.match(/\b(image|picture|photo)\b/i)) { + component.warn(this, { + code: `a11y-img-redundant-alt`, + message: `A11y: Screenreaders already announce elements as an image.` + }); } } } diff --git a/test/validator/samples/a11y-figcaption-in-non-element-block/input.svelte b/test/validator/samples/a11y-figcaption-in-non-element-block/input.svelte index 9d4b6ded4d..33e7a891f0 100644 --- a/test/validator/samples/a11y-figcaption-in-non-element-block/input.svelte +++ b/test/validator/samples/a11y-figcaption-in-non-element-block/input.svelte @@ -3,7 +3,7 @@
    - a picture of a foo + a foo {#if caption}
    {caption}
    {/if} diff --git a/test/validator/samples/a11y-figcaption-right-place/input.svelte b/test/validator/samples/a11y-figcaption-right-place/input.svelte index 808dbee941..783219c8f8 100644 --- a/test/validator/samples/a11y-figcaption-right-place/input.svelte +++ b/test/validator/samples/a11y-figcaption-right-place/input.svelte @@ -1,5 +1,5 @@
    - a picture of a foo + a foo
    a foo in its natural habitat diff --git a/test/validator/samples/a11y-figcaption-wrong-place/input.svelte b/test/validator/samples/a11y-figcaption-wrong-place/input.svelte index ffa7dde65d..e99d1cc86c 100644 --- a/test/validator/samples/a11y-figcaption-wrong-place/input.svelte +++ b/test/validator/samples/a11y-figcaption-wrong-place/input.svelte @@ -1,5 +1,5 @@
    - a picture of a foo + a foo
    a foo in its natural habitat @@ -9,7 +9,7 @@
    - a picture of a foo + a foo
    diff --git a/test/validator/samples/a11y-figcaption-wrong-place/warnings.json b/test/validator/samples/a11y-figcaption-wrong-place/warnings.json index ee5a89d3ff..eba5b6f31e 100644 --- a/test/validator/samples/a11y-figcaption-wrong-place/warnings.json +++ b/test/validator/samples/a11y-figcaption-wrong-place/warnings.json @@ -5,14 +5,14 @@ "start": { "line": 4, "column": 1, - "character": 57 + "character": 44 }, "end": { "line": 6, "column": 14, - "character": 115 + "character": 102 }, - "pos": 57 + "pos": 44 }, { "code": "a11y-structure", @@ -20,13 +20,13 @@ "start": { "line": 15, "column": 2, - "character": 252 + "character": 226 }, "end": { "line": 17, "column": 15, - "character": 328 + "character": 302 }, - "pos": 252 + "pos": 226 } ] diff --git a/test/validator/samples/a11y-img-redundant-alt/input.svelte b/test/validator/samples/a11y-img-redundant-alt/input.svelte new file mode 100644 index 0000000000..3ba029844d --- /dev/null +++ b/test/validator/samples/a11y-img-redundant-alt/input.svelte @@ -0,0 +1,7 @@ +Foo eating a sandwich. +Picture of me taking a photo of an image +Photo of foo being weird. +Image of me at a bar! +Picture of baz fixing a bug. +Plant doing photosynthesis in the afternoon +Picturesque food \ No newline at end of file diff --git a/test/validator/samples/a11y-img-redundant-alt/warnings.json b/test/validator/samples/a11y-img-redundant-alt/warnings.json new file mode 100644 index 0000000000..44106c23d0 --- /dev/null +++ b/test/validator/samples/a11y-img-redundant-alt/warnings.json @@ -0,0 +1,47 @@ +[ + { + "code": "a11y-img-redundant-alt", + "message": "A11y: Screenreaders already announce elements as an image.", + "end": { + "character": 173, + "column": 49, + "line": 3 + }, + "start": { + "character": 124, + "column": 0, + "line": 3 + }, + "pos": 124 + }, + { + "code": "a11y-img-redundant-alt", + "message": "A11y: Screenreaders already announce elements as an image.", + "end": { + "character": 219, + "column": 45, + "line": 4 + }, + "start": { + "character": 174, + "column": 0, + "line": 4 + }, + "pos": 174 + }, + { + "code": "a11y-img-redundant-alt", + "message": "A11y: Screenreaders already announce elements as an image.", + "end": { + "character": 272, + "column": 52, + "line": 5 + }, + "start": { + "character": 220, + "column": 0, + "line": 5 + }, + "pos": 220 + } +] \ No newline at end of file From 9179c3e99bbd8a79b9000067feb07edf81dd7e96 Mon Sep 17 00:00:00 2001 From: Conduitry Date: Sun, 3 May 2020 12:08:25 -0400 Subject: [PATCH 41/69] update changelog --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d3f99af139..ba58df5b82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,8 @@ * Fix reactivity with imported values that are then mutated ([#4555](https://github.com/sveltejs/svelte/issues/4555)) * Do not display a11y warning about missing `href` for `` with `name` or `id` ([#4697](https://github.com/sveltejs/svelte/issues/4697)) * Disable infinite loop guard inside generators ([#4698](https://github.com/sveltejs/svelte/issues/4698)) -* Display a11y warning for `href="javascript:..."` ([#4733](https://github.com/sveltejs/svelte/pull/4733)) +* Display `a11y-invalid-attribute` warning for `href="javascript:..."` ([#4733](https://github.com/sveltejs/svelte/pull/4733)) +* Implement `a11y-img-redundant-alt` warning ([#4750](https://github.com/sveltejs/svelte/pull/4750)) * Fix variable name conflict with component called `` ([#4768](https://github.com/sveltejs/svelte/issues/4768)) ## 3.21.0 From 5efeeecee83aca2064aeca0d9e7f7ec949410774 Mon Sep 17 00:00:00 2001 From: Joseph Abrahamson Date: Sun, 3 May 2020 12:13:15 -0400 Subject: [PATCH 42/69] docs: clarify store contract type signature (#4539) --- site/content/docs/01-component-format.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/content/docs/01-component-format.md b/site/content/docs/01-component-format.md index 07a59c69f8..fa851ccc41 100644 --- a/site/content/docs/01-component-format.md +++ b/site/content/docs/01-component-format.md @@ -175,7 +175,7 @@ Local variables (that do not represent store values) must *not* have a `$` prefi ##### Store contract ```js -store = { subscribe: (subscription: (value: any) => void) => () => void, set?: (value: any) => void } +store = { subscribe: (subscription: (value: any) => void) => (() => void), set?: (value: any) => void } ``` You can create your own stores without relying on [`svelte/store`](docs#svelte_store), by implementing the *store contract*: From a73be39a805e8b39adaa4314af38c5a635d56439 Mon Sep 17 00:00:00 2001 From: Th0rN13 Date: Sun, 3 May 2020 21:41:51 +0500 Subject: [PATCH 43/69] fix contextual dynamic bind:this in {#each} (#4759) --- .../render_dom/wrappers/shared/bind_this.ts | 3 +++ .../binding-this-each-object-props/_config.js | 14 ++++++++++++++ .../binding-this-each-object-props/main.svelte | 13 +++++++++++++ .../binding-this-each-object-spread/_config.js | 14 ++++++++++++++ .../binding-this-each-object-spread/main.svelte | 13 +++++++++++++ 5 files changed, 57 insertions(+) create mode 100644 test/runtime/samples/binding-this-each-object-props/_config.js create mode 100644 test/runtime/samples/binding-this-each-object-props/main.svelte create mode 100644 test/runtime/samples/binding-this-each-object-spread/_config.js create mode 100644 test/runtime/samples/binding-this-each-object-spread/main.svelte diff --git a/src/compiler/compile/render_dom/wrappers/shared/bind_this.ts b/src/compiler/compile/render_dom/wrappers/shared/bind_this.ts index 97199a71c2..8a9830b01b 100644 --- a/src/compiler/compile/render_dom/wrappers/shared/bind_this.ts +++ b/src/compiler/compile/render_dom/wrappers/shared/bind_this.ts @@ -55,6 +55,9 @@ export default function bind_this(component: Component, block: Block, binding: B const args = []; for (const id of contextual_dependencies) { args.push(id); + if (block.variables.has(id.name)) { + if (block.renderer.context_lookup.get(id.name).is_contextual) continue; + } block.add_variable(id, block.renderer.reference(id.name)); } diff --git a/test/runtime/samples/binding-this-each-object-props/_config.js b/test/runtime/samples/binding-this-each-object-props/_config.js new file mode 100644 index 0000000000..5372667b82 --- /dev/null +++ b/test/runtime/samples/binding-this-each-object-props/_config.js @@ -0,0 +1,14 @@ +export default { + html: ``, + + async test({ assert, component, target }) { + component.visible = true; + assert.htmlEqual(target.innerHTML, ` +
    b
    b
    c
    c
    + `); + assert.equal(component.items1[1], target.querySelector('div')); + assert.equal(component.items2[1], target.querySelector('div:nth-child(2)')); + assert.equal(component.items1[2], target.querySelector('div:nth-child(3)')); + assert.equal(component.items2[2], target.querySelector('div:last-child')); + } +}; diff --git a/test/runtime/samples/binding-this-each-object-props/main.svelte b/test/runtime/samples/binding-this-each-object-props/main.svelte new file mode 100644 index 0000000000..9654a58418 --- /dev/null +++ b/test/runtime/samples/binding-this-each-object-props/main.svelte @@ -0,0 +1,13 @@ + + +{#each data as item (item.id)} +
    {item.text}
    +
    {item.text}
    +{/each} diff --git a/test/runtime/samples/binding-this-each-object-spread/_config.js b/test/runtime/samples/binding-this-each-object-spread/_config.js new file mode 100644 index 0000000000..cecec08db9 --- /dev/null +++ b/test/runtime/samples/binding-this-each-object-spread/_config.js @@ -0,0 +1,14 @@ +export default { + html: ``, + + async test({ assert, component, target }) { + component.visible = true; + assert.htmlEqual(target.innerHTML, ` +
    a
    a
    b
    b
    + `); + assert.equal(component.items1[1], target.querySelector('div')); + assert.equal(component.items2[1], target.querySelector('div:nth-child(2)')); + assert.equal(component.items1[2], target.querySelector('div:nth-child(3)')); + assert.equal(component.items2[2], target.querySelector('div:last-child')); + } +}; diff --git a/test/runtime/samples/binding-this-each-object-spread/main.svelte b/test/runtime/samples/binding-this-each-object-spread/main.svelte new file mode 100644 index 0000000000..256ed0ede6 --- /dev/null +++ b/test/runtime/samples/binding-this-each-object-spread/main.svelte @@ -0,0 +1,13 @@ + + +{#each data as {id, text} (id)} +
    {text}
    +
    {text}
    +{/each} From 3df407447f9641496867bf8fe4c340376fb65f70 Mon Sep 17 00:00:00 2001 From: Conduitry Date: Sun, 3 May 2020 12:43:24 -0400 Subject: [PATCH 44/69] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ba58df5b82..87fdfdacc7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ * Fix misaligned line numbers in source maps ([#3906](https://github.com/sveltejs/svelte/issues/3906)) * Make setting a `tweened` store using `duration: 0` instantly update the value ([#4399](https://github.com/sveltejs/svelte/issues/4399)) * Fix reactivity with imported values that are then mutated ([#4555](https://github.com/sveltejs/svelte/issues/4555)) +* Fix contextual dynamic `bind:this` inside `{#each}` block ([#4686](https://github.com/sveltejs/svelte/issues/4686)) * Do not display a11y warning about missing `href` for `
    ` with `name` or `id` ([#4697](https://github.com/sveltejs/svelte/issues/4697)) * Disable infinite loop guard inside generators ([#4698](https://github.com/sveltejs/svelte/issues/4698)) * Display `a11y-invalid-attribute` warning for `href="javascript:..."` ([#4733](https://github.com/sveltejs/svelte/pull/4733)) From b9f83fd295168b12d5608c9778c183fcb53abeb6 Mon Sep 17 00:00:00 2001 From: Conduitry Date: Sun, 3 May 2020 13:17:51 -0400 Subject: [PATCH 45/69] -> v3.22.0 --- CHANGELOG.md | 2 +- package-lock.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 87fdfdacc7..b53b5bc0f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Svelte changelog -## Unreleased +## 3.22.0 * Fix misaligned line numbers in source maps ([#3906](https://github.com/sveltejs/svelte/issues/3906)) * Make setting a `tweened` store using `duration: 0` instantly update the value ([#4399](https://github.com/sveltejs/svelte/issues/4399)) diff --git a/package-lock.json b/package-lock.json index d5adaa34ca..91e9b8fad1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "svelte", - "version": "3.21.0", + "version": "3.22.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 215a6099e9..4e859128e4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "svelte", - "version": "3.21.0", + "version": "3.22.0", "description": "Cybernetically enhanced web apps", "module": "index.mjs", "main": "index", From ba89c29b988740f36ceefbf7631d03882018e70c Mon Sep 17 00:00:00 2001 From: Doga Genc Date: Sun, 3 May 2020 23:28:53 +0300 Subject: [PATCH 46/69] fix img-reduntant-alt bug (#4771) --- src/compiler/compile/nodes/Element.ts | 6 +++--- test/validator/samples/a11y-img-redundant-alt/input.svelte | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/compiler/compile/nodes/Element.ts b/src/compiler/compile/nodes/Element.ts index 93997ae66e..5cda9919af 100644 --- a/src/compiler/compile/nodes/Element.ts +++ b/src/compiler/compile/nodes/Element.ts @@ -438,7 +438,7 @@ export default class Element extends Node { if (href_attribute) { const href_value = href_attribute.get_static_value(); - + if (href_value === '' || href_value === '#' || /^\W*javascript:/i.test(href_value)) { component.warn(href_attribute, { code: `a11y-invalid-attribute`, @@ -484,11 +484,11 @@ export default class Element extends Node { const aria_hidden_attribute = attribute_map.get('aria-hidden'); const aria_hidden_exist = aria_hidden_attribute && aria_hidden_attribute.get_static_value(); - + if (alt_attribute && !aria_hidden_exist) { const alt_value = alt_attribute.get_static_value(); - if (alt_value.match(/\b(image|picture|photo)\b/i)) { + if (alt_value && alt_value.match(/\b(image|picture|photo)\b/i)) { component.warn(this, { code: `a11y-img-redundant-alt`, message: `A11y: Screenreaders already announce elements as an image.` diff --git a/test/validator/samples/a11y-img-redundant-alt/input.svelte b/test/validator/samples/a11y-img-redundant-alt/input.svelte index 3ba029844d..cd942ce2a0 100644 --- a/test/validator/samples/a11y-img-redundant-alt/input.svelte +++ b/test/validator/samples/a11y-img-redundant-alt/input.svelte @@ -4,4 +4,5 @@ Image of me at a bar! Picture of baz fixing a bug. Plant doing photosynthesis in the afternoon -Picturesque food \ No newline at end of file +Picturesque food +{'baz'} From bb5e2d2f262867ad0e96f7d85f8ab9013c853a3b Mon Sep 17 00:00:00 2001 From: Conduitry Date: Sun, 3 May 2020 16:30:19 -0400 Subject: [PATCH 47/69] -> v3.22.1 --- CHANGELOG.md | 4 ++++ package-lock.json | 2 +- package.json | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b53b5bc0f6..bc36b2eb06 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Svelte changelog +## 3.22.1 + +* Fix compiler exception with `a11y-img-redundant-alt` and dynamic `alt` attribute ([#4770](https://github.com/sveltejs/svelte/issues/4770)) + ## 3.22.0 * Fix misaligned line numbers in source maps ([#3906](https://github.com/sveltejs/svelte/issues/3906)) diff --git a/package-lock.json b/package-lock.json index 91e9b8fad1..199e237df9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "svelte", - "version": "3.22.0", + "version": "3.22.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 4e859128e4..990c264363 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "svelte", - "version": "3.22.0", + "version": "3.22.1", "description": "Cybernetically enhanced web apps", "module": "index.mjs", "main": "index", From bdabd89f093afe06d5a4bfb8ee0362b0a9634cfc Mon Sep 17 00:00:00 2001 From: Conduitry Date: Mon, 4 May 2020 11:23:04 -0400 Subject: [PATCH 48/69] fix exception with empty alt attribute (#4778) --- CHANGELOG.md | 4 ++++ src/compiler/compile/nodes/Element.ts | 2 +- test/validator/samples/a11y-img-redundant-alt/input.svelte | 1 + 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bc36b2eb06..2b43394564 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Svelte changelog +## Unreleased + +* Fix compiler exception with `a11y-img-redundant-alt` and value-less `alt` attribute ([#4777](https://github.com/sveltejs/svelte/issues/4777)) + ## 3.22.1 * Fix compiler exception with `a11y-img-redundant-alt` and dynamic `alt` attribute ([#4770](https://github.com/sveltejs/svelte/issues/4770)) diff --git a/src/compiler/compile/nodes/Element.ts b/src/compiler/compile/nodes/Element.ts index 5cda9919af..a337eff73a 100644 --- a/src/compiler/compile/nodes/Element.ts +++ b/src/compiler/compile/nodes/Element.ts @@ -488,7 +488,7 @@ export default class Element extends Node { if (alt_attribute && !aria_hidden_exist) { const alt_value = alt_attribute.get_static_value(); - if (alt_value && alt_value.match(/\b(image|picture|photo)\b/i)) { + if (/\b(image|picture|photo)\b/i.test(alt_value)) { component.warn(this, { code: `a11y-img-redundant-alt`, message: `A11y: Screenreaders already announce elements as an image.` diff --git a/test/validator/samples/a11y-img-redundant-alt/input.svelte b/test/validator/samples/a11y-img-redundant-alt/input.svelte index cd942ce2a0..a3528a5c41 100644 --- a/test/validator/samples/a11y-img-redundant-alt/input.svelte +++ b/test/validator/samples/a11y-img-redundant-alt/input.svelte @@ -6,3 +6,4 @@ Plant doing photosynthesis in the afternoon Picturesque food {'baz'} + From c743e72a1eaa5710ab510841e9906e27879fc539 Mon Sep 17 00:00:00 2001 From: Conduitry Date: Mon, 4 May 2020 11:23:38 -0400 Subject: [PATCH 49/69] -> v3.22.2 --- CHANGELOG.md | 2 +- package-lock.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b43394564..4f3d47d73b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Svelte changelog -## Unreleased +## 3.22.2 * Fix compiler exception with `a11y-img-redundant-alt` and value-less `alt` attribute ([#4777](https://github.com/sveltejs/svelte/issues/4777)) diff --git a/package-lock.json b/package-lock.json index 199e237df9..937f04f5f4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "svelte", - "version": "3.22.1", + "version": "3.22.2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 990c264363..91bf708676 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "svelte", - "version": "3.22.1", + "version": "3.22.2", "description": "Cybernetically enhanced web apps", "module": "index.mjs", "main": "index", From 37cc5888f8db1f281a45df0395e8defc9180753a Mon Sep 17 00:00:00 2001 From: Tan Li Hau Date: Mon, 11 May 2020 23:37:27 +0800 Subject: [PATCH 50/69] dry {#each}/{#await} destructuring (#4596) --- CHANGELOG.md | 4 + src/compiler/compile/nodes/AwaitBlock.ts | 39 +-- src/compiler/compile/nodes/CatchBlock.ts | 6 +- src/compiler/compile/nodes/EachBlock.ts | 52 +--- src/compiler/compile/nodes/ThenBlock.ts | 6 +- src/compiler/compile/nodes/shared/Context.ts | 58 ++++ .../compile/render_dom/wrappers/AwaitBlock.ts | 95 ++++--- .../compile/render_ssr/handlers/AwaitBlock.ts | 2 +- .../utils/traverse_destructure_pattern.ts | 35 --- src/compiler/parse/index.ts | 48 ---- src/compiler/parse/read/context.ts | 260 +++++------------- src/compiler/parse/state/mustache.ts | 6 +- test/parser/samples/await-catch/output.json | 2 + .../samples/await-then-catch/output.json | 4 + .../each-block-destructured/input.svelte | 4 + .../each-block-destructured/output.json | 221 ++++++++++++--- .../no-error-if-before-closing/output.json | 4 + .../_config.js | 22 ++ .../main.svelte | 7 + .../errors.json | 2 +- .../errors.json | 4 +- .../errors.json | 4 +- 22 files changed, 453 insertions(+), 432 deletions(-) create mode 100644 src/compiler/compile/nodes/shared/Context.ts delete mode 100644 src/compiler/compile/utils/traverse_destructure_pattern.ts create mode 100644 test/runtime/samples/each-block-destructured-default/_config.js create mode 100644 test/runtime/samples/each-block-destructured-default/main.svelte diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f3d47d73b..cb9bbf6b8f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Svelte changelog +## Unreleased + +* Support default values and trailing commas in destructuring `{#await}` ([#4560](https://github.com/sveltejs/svelte/issues/4560), [#4810](https://github.com/sveltejs/svelte/issues/4810)) + ## 3.22.2 * Fix compiler exception with `a11y-img-redundant-alt` and value-less `alt` attribute ([#4777](https://github.com/sveltejs/svelte/issues/4777)) diff --git a/src/compiler/compile/nodes/AwaitBlock.ts b/src/compiler/compile/nodes/AwaitBlock.ts index a7b8fb815e..66bc15364c 100644 --- a/src/compiler/compile/nodes/AwaitBlock.ts +++ b/src/compiler/compile/nodes/AwaitBlock.ts @@ -3,17 +3,21 @@ import PendingBlock from './PendingBlock'; import ThenBlock from './ThenBlock'; import CatchBlock from './CatchBlock'; import Expression from './shared/Expression'; -import { Pattern } from 'estree'; import Component from '../Component'; import TemplateScope from './shared/TemplateScope'; import { TemplateNode } from '../../interfaces'; -import traverse_destructure_pattern from '../utils/traverse_destructure_pattern'; +import { Context, unpack_destructuring } from './shared/Context'; +import { Node as ESTreeNode } from 'estree'; export default class AwaitBlock extends Node { type: 'AwaitBlock'; expression: Expression; - value: DestructurePattern; - error: DestructurePattern; + + then_contexts: Context[]; + catch_contexts: Context[]; + + then_node: ESTreeNode | null; + catch_node: ESTreeNode | null; pending: PendingBlock; then: ThenBlock; @@ -24,24 +28,21 @@ export default class AwaitBlock extends Node { this.expression = new Expression(component, this, scope, info.expression); - this.value = info.value && new DestructurePattern(info.value); - this.error = info.error && new DestructurePattern(info.error); + this.then_node = info.value; + this.catch_node = info.error; + + if (this.then_node) { + this.then_contexts = []; + unpack_destructuring(this.then_contexts, info.value, node => node); + } + + if (this.catch_node) { + this.catch_contexts = []; + unpack_destructuring(this.catch_contexts, info.error, node => node); + } this.pending = new PendingBlock(component, this, scope, info.pending); this.then = new ThenBlock(component, this, scope, info.then); this.catch = new CatchBlock(component, this, scope, info.catch); } } - -export class DestructurePattern { - pattern: Pattern; - expressions: string[]; - identifier_name: string | undefined; - - constructor(pattern: Pattern) { - this.pattern = pattern; - this.expressions = []; - traverse_destructure_pattern(pattern, (node) => this.expressions.push(node.name)); - this.identifier_name = this.pattern.type === 'Identifier' ? this.pattern.name : undefined; - } -} diff --git a/src/compiler/compile/nodes/CatchBlock.ts b/src/compiler/compile/nodes/CatchBlock.ts index 8b3736a2b9..1a92f617bb 100644 --- a/src/compiler/compile/nodes/CatchBlock.ts +++ b/src/compiler/compile/nodes/CatchBlock.ts @@ -13,9 +13,9 @@ export default class CatchBlock extends AbstractBlock { super(component, parent, scope, info); this.scope = scope.child(); - if (parent.error) { - parent.error.expressions.forEach(expression => { - this.scope.add(expression, parent.expression.dependencies, this); + if (parent.catch_node) { + parent.catch_contexts.forEach(context => { + this.scope.add(context.key.name, parent.expression.dependencies, this); }); } this.children = map_children(component, parent, this.scope, info.children); diff --git a/src/compiler/compile/nodes/EachBlock.ts b/src/compiler/compile/nodes/EachBlock.ts index 31850f8745..6458ea0020 100644 --- a/src/compiler/compile/nodes/EachBlock.ts +++ b/src/compiler/compile/nodes/EachBlock.ts @@ -4,56 +4,8 @@ import map_children from './shared/map_children'; import TemplateScope from './shared/TemplateScope'; import AbstractBlock from './shared/AbstractBlock'; import Element from './Element'; -import { x } from 'code-red'; -import { Node, Identifier, RestElement } from 'estree'; - -interface Context { - key: Identifier; - name?: string; - modifier: (node: Node) => Node; -} - -function unpack_destructuring(contexts: Context[], node: Node, modifier: (node: Node) => Node) { - if (!node) return; - - if (node.type === 'Identifier' || (node as any).type === 'RestIdentifier') { // TODO is this right? not RestElement? - contexts.push({ - key: node as Identifier, - modifier - }); - } else if (node.type === 'ArrayPattern') { - node.elements.forEach((element, i) => { - if (element && (element as any).type === 'RestIdentifier') { - unpack_destructuring(contexts, element, node => x`${modifier(node)}.slice(${i})` as Node); - } else { - unpack_destructuring(contexts, element, node => x`${modifier(node)}[${i}]` as Node); - } - }); - } else if (node.type === 'ObjectPattern') { - const used_properties = []; - - node.properties.forEach((property, i) => { - if ((property as any).kind === 'rest') { // TODO is this right? - const replacement: RestElement = { - type: 'RestElement', - argument: property.key as Identifier - }; - - node.properties[i] = replacement as any; - - unpack_destructuring( - contexts, - property.value, - node => x`@object_without_properties(${modifier(node)}, [${used_properties}])` as Node - ); - } else { - used_properties.push(x`"${(property.key as Identifier).name}"`); - - unpack_destructuring(contexts, property.value, node => x`${modifier(node)}.${(property.key as Identifier).name}` as Node); - } - }); - } -} +import { Context, unpack_destructuring } from './shared/Context'; +import { Node } from 'estree'; export default class EachBlock extends AbstractBlock { type: 'EachBlock'; diff --git a/src/compiler/compile/nodes/ThenBlock.ts b/src/compiler/compile/nodes/ThenBlock.ts index 7eefe2e6fb..720f88ad78 100644 --- a/src/compiler/compile/nodes/ThenBlock.ts +++ b/src/compiler/compile/nodes/ThenBlock.ts @@ -13,9 +13,9 @@ export default class ThenBlock extends AbstractBlock { super(component, parent, scope, info); this.scope = scope.child(); - if (parent.value) { - parent.value.expressions.forEach(expression => { - this.scope.add(expression, parent.expression.dependencies, this); + if (parent.then_node) { + parent.then_contexts.forEach(context => { + this.scope.add(context.key.name, parent.expression.dependencies, this); }); } this.children = map_children(component, parent, this.scope, info.children); diff --git a/src/compiler/compile/nodes/shared/Context.ts b/src/compiler/compile/nodes/shared/Context.ts new file mode 100644 index 0000000000..797405b0fe --- /dev/null +++ b/src/compiler/compile/nodes/shared/Context.ts @@ -0,0 +1,58 @@ +import { x } from 'code-red'; +import { Node, Identifier, RestElement, Property } from 'estree'; + +export interface Context { + key: Identifier; + name?: string; + modifier: (node: Node) => Node; +} + +export function unpack_destructuring(contexts: Context[], node: Node, modifier: (node: Node) => Node) { + if (!node) return; + + if (node.type === 'Identifier') { + contexts.push({ + key: node as Identifier, + modifier + }); + } else if (node.type === 'RestElement') { + contexts.push({ + key: node.argument as Identifier, + 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); + } else if (element && element.type === 'AssignmentPattern') { + unpack_destructuring(contexts, element.left, node => x`${modifier(node)}[${i}] !== undefined ? ${modifier(node)}[${i}] : ${element.right}` as Node); + } else { + unpack_destructuring(contexts, element, node => x`${modifier(node)}[${i}]` as Node); + } + }); + } else if (node.type === 'ObjectPattern') { + const used_properties = []; + + node.properties.forEach((property) => { + const props: (RestElement | Property) = (property as any); + + if (props.type === 'RestElement') { + unpack_destructuring( + contexts, + props.argument, + node => x`@object_without_properties(${modifier(node)}, [${used_properties}])` as Node + ); + } else { + const key = property.key as Identifier; + const value = property.value; + + used_properties.push(x`"${(key as Identifier).name}"`); + if (value.type === 'AssignmentPattern') { + unpack_destructuring(contexts, value.left, node => x`${modifier(node)}.${key.name} !== undefined ? ${modifier(node)}.${key.name} : ${value.right}` as Node); + } else { + unpack_destructuring(contexts, value, node => x`${modifier(node)}.${key.name}` as Node); + } + } + }); + } +} diff --git a/src/compiler/compile/render_dom/wrappers/AwaitBlock.ts b/src/compiler/compile/render_dom/wrappers/AwaitBlock.ts index 1898a840d2..ceb898bf79 100644 --- a/src/compiler/compile/render_dom/wrappers/AwaitBlock.ts +++ b/src/compiler/compile/render_dom/wrappers/AwaitBlock.ts @@ -8,27 +8,37 @@ import FragmentWrapper from './Fragment'; import PendingBlock from '../../nodes/PendingBlock'; import ThenBlock from '../../nodes/ThenBlock'; import CatchBlock from '../../nodes/CatchBlock'; -import { Identifier } from 'estree'; -import traverse_destructure_pattern from '../../utils/traverse_destructure_pattern'; +import { Context } from '../../nodes/shared/Context'; +import { Identifier, Literal, Node } from 'estree'; + +type Status = 'pending' | 'then' | 'catch'; class AwaitBlockBranch extends Wrapper { + parent: AwaitBlockWrapper; node: PendingBlock | ThenBlock | CatchBlock; block: Block; fragment: FragmentWrapper; is_dynamic: boolean; var = null; + status: Status; + + value: string; + value_index: Literal; + value_contexts: Context[]; + is_destructured: boolean; constructor( - status: string, + status: Status, renderer: Renderer, block: Block, - parent: Wrapper, - node: AwaitBlock, + parent: AwaitBlockWrapper, + node: PendingBlock | ThenBlock | CatchBlock, strip_whitespace: boolean, next_sibling: Wrapper ) { super(renderer, block, parent, node); + this.status = status; this.block = block.child({ comment: create_debugging_comment(node, this.renderer.component), @@ -36,6 +46,8 @@ class AwaitBlockBranch extends Wrapper { type: status }); + this.add_context(parent.node[status + '_node'], parent.node[status + '_contexts']); + this.fragment = new FragmentWrapper( renderer, this.block, @@ -48,20 +60,43 @@ class AwaitBlockBranch extends Wrapper { this.is_dynamic = this.block.dependencies.size > 0; } + add_context(node: Node | null, contexts: Context[]) { + if (!node) return; + + if (node.type === 'Identifier') { + this.value = node.name; + this.renderer.add_to_context(this.value, true); + } else { + contexts.forEach(context => { + this.renderer.add_to_context(context.key.name, true); + }); + this.value = this.block.parent.get_unique_name('value').name; + this.value_contexts = contexts; + this.renderer.add_to_context(this.value, true); + this.is_destructured = true; + } + this.value_index = this.renderer.context_lookup.get(this.value).index; + } + render(block: Block, parent_node: Identifier, parent_nodes: Identifier) { this.fragment.render(block, parent_node, parent_nodes); - } - render_destructure(block: Block, value, node, index) { - if (value && node.pattern.type !== 'Identifier') { - traverse_destructure_pattern(node.pattern, (node, parent, index) => { - parent[index] = x`#ctx[${block.renderer.context_lookup.get(node.name).index}]`; - }); + if (this.is_destructured) { + this.render_destructure(); + } + } - this.block.chunks.declarations.push(b`(${node.pattern} = #ctx[${index}])`); - if (this.block.has_update_method) { - this.block.chunks.update.push(b`(${node.pattern} = #ctx[${index}])`); + render_destructure() { + const props = this.value_contexts.map(prop => b`#ctx[${this.block.renderer.context_lookup.get(prop.key.name).index}] = ${prop.modifier(x`#ctx[${this.value_index}]`)};`); + const get_context = this.block.renderer.component.get_unique_name(`get_${this.status}_context`); + this.block.renderer.blocks.push(b` + function ${get_context}(#ctx) { + ${props} } + `); + this.block.chunks.declarations.push(b`${get_context}(#ctx)`); + if (this.block.has_update_method) { + this.block.chunks.update.push(b`${get_context}(#ctx)`); } } } @@ -73,9 +108,6 @@ export default class AwaitBlockWrapper extends Wrapper { then: AwaitBlockBranch; catch: AwaitBlockBranch; - value: string; - error: string; - var: Identifier = { type: 'Identifier', name: 'await_block' }; constructor( @@ -92,26 +124,12 @@ export default class AwaitBlockWrapper extends Wrapper { this.not_static_content(); block.add_dependencies(this.node.expression.dependencies); - if (this.node.value) { - for (const ctx of this.node.value.expressions) { - block.renderer.add_to_context(ctx, true); - } - this.value = this.node.value.identifier_name || block.get_unique_name('value').name; - block.renderer.add_to_context(this.value, true); - } - if (this.node.error) { - for (const ctx of this.node.error.expressions) { - block.renderer.add_to_context(ctx, true); - } - this.error = this.node.error.identifier_name || block.get_unique_name('error').name; - block.renderer.add_to_context(this.error, true); - } let is_dynamic = false; let has_intros = false; let has_outros = false; - ['pending', 'then', 'catch'].forEach(status => { + ['pending', 'then', 'catch'].forEach((status: Status) => { const child = this.node[status]; const branch = new AwaitBlockBranch( @@ -166,9 +184,6 @@ export default class AwaitBlockWrapper extends Wrapper { block.maintain_context = true; - const value_index = this.value && block.renderer.context_lookup.get(this.value).index; - const error_index = this.error && block.renderer.context_lookup.get(this.error).index; - const info_props: any = x`{ ctx: #ctx, current: null, @@ -176,8 +191,8 @@ export default class AwaitBlockWrapper extends Wrapper { pending: ${this.pending.block.name}, then: ${this.then.block.name}, catch: ${this.catch.block.name}, - value: ${value_index}, - error: ${error_index}, + value: ${this.then.value_index}, + error: ${this.catch.value_index}, blocks: ${this.pending.block.has_outro_method && x`[,,,]`} }`; @@ -232,7 +247,7 @@ export default class AwaitBlockWrapper extends Wrapper { } else { const #child_ctx = #ctx.slice(); - ${this.value && b`#child_ctx[${value_index}] = ${info}.resolved;`} + ${this.then.value && b`#child_ctx[${this.then.value_index}] = ${info}.resolved;`} ${info}.block.p(#child_ctx, #dirty); } `); @@ -246,7 +261,7 @@ export default class AwaitBlockWrapper extends Wrapper { block.chunks.update.push(b` { const #child_ctx = #ctx.slice(); - ${this.value && b`#child_ctx[${value_index}] = ${info}.resolved;`} + ${this.then.value && b`#child_ctx[${this.then.value_index}] = ${info}.resolved;`} ${info}.block.p(#child_ctx, #dirty); } `); @@ -271,7 +286,5 @@ export default class AwaitBlockWrapper extends Wrapper { [this.pending, this.then, this.catch].forEach(branch => { branch.render(branch.block, null, x`#nodes` as Identifier); }); - this.then.render_destructure(block, this.value, this.node.value, value_index); - this.catch.render_destructure(block, this.error, this.node.error, error_index); } } diff --git a/src/compiler/compile/render_ssr/handlers/AwaitBlock.ts b/src/compiler/compile/render_ssr/handlers/AwaitBlock.ts index d6bb86f1a4..6a62f872bb 100644 --- a/src/compiler/compile/render_ssr/handlers/AwaitBlock.ts +++ b/src/compiler/compile/render_ssr/handlers/AwaitBlock.ts @@ -14,7 +14,7 @@ export default function(node: AwaitBlock, renderer: Renderer, options: RenderOpt renderer.add_expression(x` function(__value) { if (@is_promise(__value)) return ${pending}; - return (function(${node.value ? node.value.pattern : ''}) { return ${then}; }(__value)); + return (function(${node.then_node ? node.then_node : ''}) { return ${then}; }(__value)); }(${node.expression.node}) `); } diff --git a/src/compiler/compile/utils/traverse_destructure_pattern.ts b/src/compiler/compile/utils/traverse_destructure_pattern.ts deleted file mode 100644 index 6c918c28c0..0000000000 --- a/src/compiler/compile/utils/traverse_destructure_pattern.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { Pattern, Identifier, RestElement } from "estree"; -import { Node } from "acorn"; - -export default function traverse_destructure_pattern( - node: Pattern, - callback: (node: Identifier, parent: Node, key: string | number) => void -) { - function traverse(node: Pattern, parent, key) { - switch (node.type) { - case "Identifier": - return callback(node, parent, key); - case "ArrayPattern": - for (let i = 0; i < node.elements.length; i++) { - const element = node.elements[i]; - traverse(element, node.elements, i); - } - break; - case "ObjectPattern": - for (let i = 0; i < node.properties.length; i++) { - const property = node.properties[i]; - if (property.type === "Property") { - traverse(property.value, property, "value"); - } else { - traverse((property as any) as RestElement, node.properties, i); - } - } - break; - case "RestElement": - return traverse(node.argument, node, 'argument'); - case "AssignmentPattern": - return traverse(node.left, node, 'left'); - } - } - traverse(node, null, null); -} diff --git a/src/compiler/parse/index.ts b/src/compiler/parse/index.ts index c21e6d6f79..a809eeebeb 100644 --- a/src/compiler/parse/index.ts +++ b/src/compiler/parse/index.ts @@ -5,9 +5,6 @@ import { reserved } from '../utils/names'; import full_char_code_at from '../utils/full_char_code_at'; import { TemplateNode, Ast, ParserOptions, Fragment, Style, Script } from '../interfaces'; import error from '../utils/error'; -import { is_bracket_open, is_bracket_close, is_bracket_pair, get_bracket_close } from './utils/bracket'; -import { parse_expression_at } from './acorn'; -import { Pattern } from 'estree'; type ParserState = (parser: Parser) => (ParserState | void); @@ -173,51 +170,6 @@ export class Parser { return identifier; } - read_destructure_pattern(): Pattern { - const start = this.index; - let i = this.index; - - const code = full_char_code_at(this.template, i); - if (isIdentifierStart(code, true)) { - return { type: 'Identifier', name: this.read_identifier() }; - } - - if (!is_bracket_open(code)) { - this.error({ - code: 'unexpected-token', - message: 'Expected identifier or destructure pattern', - }); - } - - const bracket_stack = [code]; - i += code <= 0xffff ? 1 : 2; - - while (i < this.template.length) { - const code = full_char_code_at(this.template, i); - if (is_bracket_open(code)) { - bracket_stack.push(code); - } else if (is_bracket_close(code)) { - if (!is_bracket_pair(bracket_stack[bracket_stack.length - 1], code)) { - this.error({ - code: 'unexpected-token', - message: `Expected ${String.fromCharCode(get_bracket_close(bracket_stack[bracket_stack.length - 1]))}` - }); - } - bracket_stack.pop(); - if (bracket_stack.length === 0) { - i += code <= 0xffff ? 1 : 2; - break; - } - } - i += code <= 0xffff ? 1 : 2; - } - - this.index = i; - - const pattern_string = this.template.slice(start, i); - return (parse_expression_at(`(${pattern_string} = 1)`, 0) as any).left as Pattern; - } - read_until(pattern: RegExp) { if (this.index >= this.template.length) this.error({ diff --git a/src/compiler/parse/read/context.ts b/src/compiler/parse/read/context.ts index fe666467f8..8d28bd2024 100644 --- a/src/compiler/parse/read/context.ts +++ b/src/compiler/parse/read/context.ts @@ -1,202 +1,82 @@ -import { Parser } from '../index'; -import { reserved } from '../../utils/names'; - -interface Identifier { - start: number; - end: number; - type: 'Identifier'; - name: string; -} - -interface Property { - start: number; - end: number; - type: 'Property'; - kind: 'init' | 'rest'; - shorthand: boolean; - key: Identifier; - value: Context; -} - -interface Context { - start: number; - end: number; - type: 'Identifier' | 'ArrayPattern' | 'ObjectPattern' | 'RestIdentifier'; - name?: string; - elements?: Context[]; - properties?: Property[]; -} +import { Parser } from "../index"; +import { isIdentifierStart } from "acorn"; +import full_char_code_at from "../../utils/full_char_code_at"; +import { + is_bracket_open, + is_bracket_close, + is_bracket_pair, + get_bracket_close +} from "../utils/bracket"; +import { parse_expression_at } from "../acorn"; +import { Pattern } from "estree"; + +export default function read_context( + parser: Parser +): Pattern & { start: number; end: number } { + const start = parser.index; + let i = parser.index; + + const code = full_char_code_at(parser.template, i); + if (isIdentifierStart(code, true)) { + return { + type: "Identifier", + name: parser.read_identifier(), + start, + end: parser.index + }; + } -function error_on_assignment_pattern(parser: Parser) { - if (parser.eat('=')) { + if (!is_bracket_open(code)) { parser.error({ - code: 'invalid-assignment-pattern', - message: 'Assignment patterns are not supported' - }, parser.index - 1); + code: "unexpected-token", + message: "Expected identifier or destructure pattern" + }); } -} - -function error_on_rest_pattern_not_last(parser: Parser) { - parser.error({ - code: 'rest-pattern-not-last', - message: 'Rest destructuring expected to be last' - }, parser.index); -} - -export default function read_context(parser: Parser) { - const context: Context = { - start: parser.index, - end: null, - type: null - }; - if (parser.eat('[')) { - context.type = 'ArrayPattern'; - context.elements = []; - - do { - parser.allow_whitespace(); - - const lastContext = context.elements[context.elements.length - 1]; - if (lastContext && lastContext.type === 'RestIdentifier') { - error_on_rest_pattern_not_last(parser); + const bracket_stack = [code]; + i += code <= 0xffff ? 1 : 2; + + while (i < parser.template.length) { + const code = full_char_code_at(parser.template, i); + if (is_bracket_open(code)) { + bracket_stack.push(code); + } else if (is_bracket_close(code)) { + if (!is_bracket_pair(bracket_stack[bracket_stack.length - 1], code)) { + parser.error({ + code: "unexpected-token", + message: `Expected ${String.fromCharCode( + get_bracket_close(bracket_stack[bracket_stack.length - 1]) + )}` + }); } - - if (parser.template[parser.index] === ',') { - context.elements.push(null); - } else { - context.elements.push(read_context(parser)); - parser.allow_whitespace(); - } - } while (parser.eat(',')); - - error_on_assignment_pattern(parser); - parser.eat(']', true); - context.end = parser.index; - } - - else if (parser.eat('{')) { - context.type = 'ObjectPattern'; - context.properties = []; - - do { - parser.allow_whitespace(); - - if (parser.eat('...')) { - parser.allow_whitespace(); - - const start = parser.index; - const name = parser.read_identifier(); - const key: Identifier = { - start, - end: parser.index, - type: 'Identifier', - name - }; - const property: Property = { - start, - end: parser.index, - type: 'Property', - kind: 'rest', - shorthand: true, - key, - value: key - }; - - context.properties.push(property); - - parser.allow_whitespace(); - - if (parser.eat(',')) { - parser.error({ - code: `comma-after-rest`, - message: `Comma is not permitted after the rest element` - }, parser.index - 1); - } - + bracket_stack.pop(); + if (bracket_stack.length === 0) { + i += code <= 0xffff ? 1 : 2; break; } - - // TODO: DRY this out somehow - // We don't know whether we want to allow reserved words until we see whether there's a ':' after it - // Probably ideally we'd use Acorn to do all of this - const start = parser.index; - const name = parser.read_identifier(true); - const key: Identifier = { - start, - end: parser.index, - type: 'Identifier', - name - }; - parser.allow_whitespace(); - - let value: Context; - if (parser.eat(':')) { - parser.allow_whitespace(); - value = read_context(parser); - } else { - if (reserved.has(name)) { - parser.error({ - code: `unexpected-reserved-word`, - message: `'${name}' is a reserved word in JavaScript and cannot be used here` - }, start); - } - value = key; - } - - const property: Property = { - start, - end: value.end, - type: 'Property', - kind: 'init', - shorthand: value.type === 'Identifier' && value.name === name, - key, - value - }; - - context.properties.push(property); - - parser.allow_whitespace(); - } while (parser.eat(',')); - - error_on_assignment_pattern(parser); - parser.eat('}', true); - context.end = parser.index; - } - - else if (parser.eat('...')) { - const name = parser.read_identifier(); - if (name) { - context.type = 'RestIdentifier'; - context.end = parser.index; - context.name = name; - } - - else { - parser.error({ - code: 'invalid-context', - message: 'Expected a rest pattern' - }); } + i += code <= 0xffff ? 1 : 2; } - else { - const name = parser.read_identifier(); - if (name) { - context.type = 'Identifier'; - context.end = parser.index; - context.name = name; - } - - else { - parser.error({ - code: 'invalid-context', - message: 'Expected a name, array pattern or object pattern' - }); - } - - error_on_assignment_pattern(parser); + parser.index = i; + + const pattern_string = parser.template.slice(start, i); + try { + // the length of the `space_with_newline` has to be start - 1 + // because we added a `(` in front of the pattern_string, + // which shifted the entire string to right by 1 + // so we offset it by removing 1 character in the `space_with_newline` + // to achieve that, we remove the 1st space encountered, + // so it will not affect the `column` of the node + let space_with_newline = parser.template.slice(0, start).replace(/[^\n]/g, ' '); + const first_space = space_with_newline.indexOf(' '); + space_with_newline = space_with_newline.slice(0, first_space) + space_with_newline.slice(first_space + 1); + + return (parse_expression_at( + `${space_with_newline}(${pattern_string} = 1)`, + start - 1 + ) as any).left; + } catch (error) { + parser.acorn_error(error); } - - return context; } diff --git a/src/compiler/parse/state/mustache.ts b/src/compiler/parse/state/mustache.ts index 0f6608f679..4e1d5c5fd4 100644 --- a/src/compiler/parse/state/mustache.ts +++ b/src/compiler/parse/state/mustache.ts @@ -196,7 +196,7 @@ export default function mustache(parser: Parser) { if (!parser.eat('}')) { parser.require_whitespace(); - await_block[is_then ? 'value': 'error'] = parser.read_destructure_pattern(); + await_block[is_then ? 'value': 'error'] = read_context(parser); parser.allow_whitespace(); parser.eat('}', true); } @@ -305,14 +305,14 @@ export default function mustache(parser: Parser) { const await_block_shorthand = type === 'AwaitBlock' && parser.eat('then'); if (await_block_shorthand) { parser.require_whitespace(); - block.value = parser.read_destructure_pattern(); + block.value = read_context(parser); parser.allow_whitespace(); } const await_block_catch_shorthand = !await_block_shorthand && type === 'AwaitBlock' && parser.eat('catch'); if (await_block_catch_shorthand) { parser.require_whitespace(); - block.error = parser.read_destructure_pattern(); + block.error = read_context(parser); parser.allow_whitespace(); } diff --git a/test/parser/samples/await-catch/output.json b/test/parser/samples/await-catch/output.json index d47d27e4cb..c543583018 100644 --- a/test/parser/samples/await-catch/output.json +++ b/test/parser/samples/await-catch/output.json @@ -26,6 +26,8 @@ }, "value": null, "error": { + "start": 47, + "end": 55, "type": "Identifier", "name": "theError" }, diff --git a/test/parser/samples/await-then-catch/output.json b/test/parser/samples/await-then-catch/output.json index 01dc89f3f9..8e4b7a4c32 100644 --- a/test/parser/samples/await-then-catch/output.json +++ b/test/parser/samples/await-then-catch/output.json @@ -25,10 +25,14 @@ "name": "thePromise" }, "value": { + "start": 46, + "end": 54, "type": "Identifier", "name": "theValue" }, "error": { + "start": 96, + "end": 104, "type": "Identifier", "name": "theError" }, diff --git a/test/parser/samples/each-block-destructured/input.svelte b/test/parser/samples/each-block-destructured/input.svelte index 09e9885be0..5313050fa0 100644 --- a/test/parser/samples/each-block-destructured/input.svelte +++ b/test/parser/samples/each-block-destructured/input.svelte @@ -1,3 +1,7 @@ + + {#each animals as [key, value, ...rest]}

    {key}: {value}

    {/each} diff --git a/test/parser/samples/each-block-destructured/output.json b/test/parser/samples/each-block-destructured/output.json index 425c609a2c..69e165c0a4 100644 --- a/test/parser/samples/each-block-destructured/output.json +++ b/test/parser/samples/each-block-destructured/output.json @@ -1,24 +1,31 @@ { "html": { - "start": 0, - "end": 71, + "start": 41, + "end": 112, "type": "Fragment", "children": [ { - "start": 0, - "end": 71, + "start": 39, + "end": 41, + "type": "Text", + "raw": "\n\n", + "data": "\n\n" + }, + { + "start": 41, + "end": 112, "type": "EachBlock", "expression": { "type": "Identifier", - "start": 7, - "end": 14, + "start": 48, + "end": 55, "loc": { "start": { - "line": 1, + "line": 5, "column": 7 }, "end": { - "line": 1, + "line": 5, "column": 14 } }, @@ -26,27 +33,27 @@ }, "children": [ { - "start": 42, - "end": 63, + "start": 83, + "end": 104, "type": "Element", "name": "p", "attributes": [], "children": [ { - "start": 45, - "end": 50, + "start": 86, + "end": 91, "type": "MustacheTag", "expression": { "type": "Identifier", - "start": 46, - "end": 49, + "start": 87, + "end": 90, "loc": { "start": { - "line": 2, + "line": 6, "column": 5 }, "end": { - "line": 2, + "line": 6, "column": 8 } }, @@ -54,27 +61,27 @@ } }, { - "start": 50, - "end": 52, + "start": 91, + "end": 93, "type": "Text", "raw": ": ", "data": ": " }, { - "start": 52, - "end": 59, + "start": 93, + "end": 100, "type": "MustacheTag", "expression": { "type": "Identifier", - "start": 53, - "end": 58, + "start": 94, + "end": 99, "loc": { "start": { - "line": 2, + "line": 6, "column": 12 }, "end": { - "line": 2, + "line": 6, "column": 17 } }, @@ -85,31 +92,177 @@ } ], "context": { - "start": 18, - "end": 39, "type": "ArrayPattern", + "start": 59, + "end": 80, + "loc": { + "start": { + "line": 5, + "column": 19 + }, + "end": { + "line": 5, + "column": 40 + } + }, "elements": [ { - "start": 19, - "end": 22, "type": "Identifier", + "start": 60, + "end": 63, + "loc": { + "start": { + "line": 5, + "column": 20 + }, + "end": { + "line": 5, + "column": 23 + } + }, "name": "key" }, { - "start": 24, - "end": 29, "type": "Identifier", + "start": 65, + "end": 70, + "loc": { + "start": { + "line": 5, + "column": 25 + }, + "end": { + "line": 5, + "column": 30 + } + }, "name": "value" }, { - "start": 31, - "end": 38, - "type": "RestIdentifier", - "name": "rest" + "type": "RestElement", + "start": 72, + "end": 79, + "loc": { + "start": { + "line": 5, + "column": 32 + }, + "end": { + "line": 5, + "column": 39 + } + }, + "argument": { + "type": "Identifier", + "start": 75, + "end": 79, + "loc": { + "start": { + "line": 5, + "column": 35 + }, + "end": { + "line": 5, + "column": 39 + } + }, + "name": "rest" + } } ] } } ] + }, + "instance": { + "type": "Script", + "start": 0, + "end": 39, + "context": "default", + "content": { + "type": "Program", + "start": 8, + "end": 30, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 3, + "column": 0 + } + }, + "body": [ + { + "type": "ExportNamedDeclaration", + "start": 10, + "end": 29, + "loc": { + "start": { + "line": 2, + "column": 1 + }, + "end": { + "line": 2, + "column": 20 + } + }, + "declaration": { + "type": "VariableDeclaration", + "start": 17, + "end": 29, + "loc": { + "start": { + "line": 2, + "column": 8 + }, + "end": { + "line": 2, + "column": 20 + } + }, + "declarations": [ + { + "type": "VariableDeclarator", + "start": 21, + "end": 28, + "loc": { + "start": { + "line": 2, + "column": 12 + }, + "end": { + "line": 2, + "column": 19 + } + }, + "id": { + "type": "Identifier", + "start": 21, + "end": 28, + "loc": { + "start": { + "line": 2, + "column": 12 + }, + "end": { + "line": 2, + "column": 19 + } + }, + "name": "animals" + }, + "init": null + } + ], + "kind": "let" + }, + "specifiers": [], + "source": null + } + ], + "sourceType": "module" + } } } \ No newline at end of file diff --git a/test/parser/samples/no-error-if-before-closing/output.json b/test/parser/samples/no-error-if-before-closing/output.json index e30c302e0f..708128a42e 100644 --- a/test/parser/samples/no-error-if-before-closing/output.json +++ b/test/parser/samples/no-error-if-before-closing/output.json @@ -116,6 +116,8 @@ "raw": "true" }, "value": { + "start": 97, + "end": 98, "type": "Identifier", "name": "f" }, @@ -202,6 +204,8 @@ "raw": "true" }, "value": { + "start": 137, + "end": 138, "type": "Identifier", "name": "f" }, diff --git a/test/runtime/samples/each-block-destructured-default/_config.js b/test/runtime/samples/each-block-destructured-default/_config.js new file mode 100644 index 0000000000..133fd68532 --- /dev/null +++ b/test/runtime/samples/each-block-destructured-default/_config.js @@ -0,0 +1,22 @@ +export default { + props: { + animalEntries: [ + { animal: 'raccoon', class: 'mammal', species: 'P. lotor', kilogram: 25 }, + { animal: 'eagle', class: 'bird', kilogram: 5.4 } + ] + }, + + html: ` +

    raccoon - P. lotor - 25kg

    +

    eagle - unknown - 5.4kg

    + `, + + + + test({ assert, component, target }) { + component.animalEntries = [{ animal: 'cow', class: 'mammal', species: '‎B. taurus' }]; + assert.htmlEqual(target.innerHTML, ` +

    cow - ‎B. taurus - 50kg

    + `); + }, +}; diff --git a/test/runtime/samples/each-block-destructured-default/main.svelte b/test/runtime/samples/each-block-destructured-default/main.svelte new file mode 100644 index 0000000000..a91b45299e --- /dev/null +++ b/test/runtime/samples/each-block-destructured-default/main.svelte @@ -0,0 +1,7 @@ + + +{#each animalEntries as { animal, species = 'unknown', kilogram: weight = 50 , ...props } } +

    {animal} - {species} - {weight}kg

    +{/each} diff --git a/test/validator/samples/each-block-destructured-object-rest-comma-after/errors.json b/test/validator/samples/each-block-destructured-object-rest-comma-after/errors.json index 549b6960eb..df899b7702 100644 --- a/test/validator/samples/each-block-destructured-object-rest-comma-after/errors.json +++ b/test/validator/samples/each-block-destructured-object-rest-comma-after/errors.json @@ -1,5 +1,5 @@ [{ - "code": "comma-after-rest", + "code": "parse-error", "message": "Comma is not permitted after the rest element", "pos": 100, "start": { diff --git a/test/validator/samples/each-block-invalid-context-destructured-object/errors.json b/test/validator/samples/each-block-invalid-context-destructured-object/errors.json index 085021ff5a..c96e3d2c8c 100644 --- a/test/validator/samples/each-block-invalid-context-destructured-object/errors.json +++ b/test/validator/samples/each-block-invalid-context-destructured-object/errors.json @@ -1,6 +1,6 @@ [{ - "code": "unexpected-reserved-word", - "message": "'case' is a reserved word in JavaScript and cannot be used here", + "code": "parse-error", + "message": "Unexpected keyword 'case'", "start": { "line": 1, "column": 18, diff --git a/test/validator/samples/each-block-invalid-context-destructured/errors.json b/test/validator/samples/each-block-invalid-context-destructured/errors.json index afe99ee219..62d6f62e87 100644 --- a/test/validator/samples/each-block-invalid-context-destructured/errors.json +++ b/test/validator/samples/each-block-invalid-context-destructured/errors.json @@ -1,6 +1,6 @@ [{ - "code": "unexpected-reserved-word", - "message": "'case' is a reserved word in JavaScript and cannot be used here", + "code": "parse-error", + "message": "Unexpected token", "start": { "line": 1, "column": 17, From 40d0ea6702c06590c10760869b65e326a44413dd Mon Sep 17 00:00:00 2001 From: Daniel Imfeld Date: Mon, 11 May 2020 06:24:01 -1000 Subject: [PATCH 51/69] set .value while setting .__value during spread (#4809) --- src/runtime/internal/dom.ts | 4 +++- .../_config.js | 15 +++++++++++++++ .../main.svelte | 6 ++++++ 3 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 test/runtime/samples/spread-element-input-bind-group-with-value-attr/_config.js create mode 100644 test/runtime/samples/spread-element-input-bind-group-with-value-attr/main.svelte diff --git a/src/runtime/internal/dom.ts b/src/runtime/internal/dom.ts index bf8fc1f5a0..e447eae362 100644 --- a/src/runtime/internal/dom.ts +++ b/src/runtime/internal/dom.ts @@ -98,7 +98,9 @@ export function set_attributes(node: Element & ElementCSSInlineStyle, attributes node.removeAttribute(key); } else if (key === 'style') { node.style.cssText = attributes[key]; - } else if (key === '__value' || descriptors[key] && descriptors[key].set) { + } else if (key === '__value') { + (node as any).value = node[key] = attributes[key]; + } else if (descriptors[key] && descriptors[key].set) { node[key] = attributes[key]; } else { attr(node, key, attributes[key]); diff --git a/test/runtime/samples/spread-element-input-bind-group-with-value-attr/_config.js b/test/runtime/samples/spread-element-input-bind-group-with-value-attr/_config.js new file mode 100644 index 0000000000..6e1b890351 --- /dev/null +++ b/test/runtime/samples/spread-element-input-bind-group-with-value-attr/_config.js @@ -0,0 +1,15 @@ +export default { + props: { + props: { + 'data-foo': 'bar' + } + }, + + html: ``, + + async test({ assert, component, target, window }) { + const input = target.querySelector('input'); + assert.equal(input.value, 'abc'); + assert.equal(input.__value, 'abc'); + } +}; diff --git a/test/runtime/samples/spread-element-input-bind-group-with-value-attr/main.svelte b/test/runtime/samples/spread-element-input-bind-group-with-value-attr/main.svelte new file mode 100644 index 0000000000..ce6b76f093 --- /dev/null +++ b/test/runtime/samples/spread-element-input-bind-group-with-value-attr/main.svelte @@ -0,0 +1,6 @@ + + + From ee130793ca69c2430d36ecf30359424ab6e2135e Mon Sep 17 00:00:00 2001 From: Conduitry Date: Mon, 11 May 2020 12:25:20 -0400 Subject: [PATCH 52/69] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb9bbf6b8f..3743f2d340 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased * Support default values and trailing commas in destructuring `{#await}` ([#4560](https://github.com/sveltejs/svelte/issues/4560), [#4810](https://github.com/sveltejs/svelte/issues/4810)) +* Fix setting `value` attribute with `bind:group` and attribute spread ([#4803](https://github.com/sveltejs/svelte/issues/4803)) ## 3.22.2 From 9be33104316ba4f111ee7aba9472eb43c546a0a7 Mon Sep 17 00:00:00 2001 From: Antony Jones Date: Wed, 13 May 2020 12:49:25 +0100 Subject: [PATCH 53/69] site: add FAQs page (#4823) --- site/content/faq/100-im-new-to-svelte.md | 7 + .../1000-how-can-i-update-my-v2-components.md | 5 + .../faq/1100-is-svelte-v2-still-availabile.md | 7 + site/content/faq/1200-how-do-i-do-hmr.md | 5 + .../faq/200-are-there-any-video-courses.md | 10 + site/content/faq/300-is-svelte-dev-down.md | 5 + .../400-how-can-i-get-syntax-highlighting.md | 5 + .../faq/500-what-about-typescript-support.md | 5 + site/content/faq/600-does-svelte-scale.md | 5 + .../faq/700-is-there-a-component-library.md | 5 + .../faq/800-how-do-i-test-svelte-apps.md | 6 + site/content/faq/900-is-there-a-router.md | 13 + site/src/routes/blog/[slug].svelte | 30 - site/src/routes/faq.js | 4 - site/src/routes/faq/_faqs.js | 58 ++ site/src/routes/faq/index.json.js | 24 + site/src/routes/faq/index.svelte | 89 +++ site/src/template.html | 1 + site/static/global.css | 513 +----------------- 19 files changed, 272 insertions(+), 525 deletions(-) create mode 100644 site/content/faq/100-im-new-to-svelte.md create mode 100644 site/content/faq/1000-how-can-i-update-my-v2-components.md create mode 100644 site/content/faq/1100-is-svelte-v2-still-availabile.md create mode 100644 site/content/faq/1200-how-do-i-do-hmr.md create mode 100644 site/content/faq/200-are-there-any-video-courses.md create mode 100644 site/content/faq/300-is-svelte-dev-down.md create mode 100644 site/content/faq/400-how-can-i-get-syntax-highlighting.md create mode 100644 site/content/faq/500-what-about-typescript-support.md create mode 100644 site/content/faq/600-does-svelte-scale.md create mode 100644 site/content/faq/700-is-there-a-component-library.md create mode 100644 site/content/faq/800-how-do-i-test-svelte-apps.md create mode 100644 site/content/faq/900-is-there-a-router.md delete mode 100644 site/src/routes/faq.js create mode 100644 site/src/routes/faq/_faqs.js create mode 100644 site/src/routes/faq/index.json.js create mode 100644 site/src/routes/faq/index.svelte diff --git a/site/content/faq/100-im-new-to-svelte.md b/site/content/faq/100-im-new-to-svelte.md new file mode 100644 index 0000000000..18e4b6742f --- /dev/null +++ b/site/content/faq/100-im-new-to-svelte.md @@ -0,0 +1,7 @@ +--- +question: I'm new to Svelte. Where should I start? +--- + +We think the best way to get started is playing through the interactive [Tutorial](https://svelte.dev/tutorial). Each step there is mainly focused on one specific aspect and is easy to follow. You'll be editing and running real Svelte components right in your browser. + +Five to ten minutes should be enough to get you up and running. An hour and a half should get you through the entire tutorial. \ No newline at end of file diff --git a/site/content/faq/1000-how-can-i-update-my-v2-components.md b/site/content/faq/1000-how-can-i-update-my-v2-components.md new file mode 100644 index 0000000000..0800dff89b --- /dev/null +++ b/site/content/faq/1000-how-can-i-update-my-v2-components.md @@ -0,0 +1,5 @@ +--- +question: How can I update my components written in Svelte v2? +--- + +svelte-upgrade isn't fully working for v2->v3 yet, [but it's close](https://github.com/sveltejs/svelte-upgrade/pull/12). \ No newline at end of file diff --git a/site/content/faq/1100-is-svelte-v2-still-availabile.md b/site/content/faq/1100-is-svelte-v2-still-availabile.md new file mode 100644 index 0000000000..0fbc4c039c --- /dev/null +++ b/site/content/faq/1100-is-svelte-v2-still-availabile.md @@ -0,0 +1,7 @@ +--- +question: Is Svelte v2 still available? +--- + +New features aren't being added to it, and bugs will probably only be fixed if they are extremely nasty or present some sort of security vulnerability. + +The documentation is still available [here](https://v2.svelte.dev/guide). \ No newline at end of file diff --git a/site/content/faq/1200-how-do-i-do-hmr.md b/site/content/faq/1200-how-do-i-do-hmr.md new file mode 100644 index 0000000000..88d08b252f --- /dev/null +++ b/site/content/faq/1200-how-do-i-do-hmr.md @@ -0,0 +1,5 @@ +--- +question: How do I do hot module reloading? +--- + +Use the community plugins for [rollup](https://github.com/rixo/rollup-plugin-svelte-hot) and [webpack](https://github.com/rixo/svelte-loader-hot). diff --git a/site/content/faq/200-are-there-any-video-courses.md b/site/content/faq/200-are-there-any-video-courses.md new file mode 100644 index 0000000000..0c2c1f680e --- /dev/null +++ b/site/content/faq/200-are-there-any-video-courses.md @@ -0,0 +1,10 @@ +--- +question: Are there any video courses? +--- + +There are no official ones, but here are a couple of third-part ones that we know of. + +- [Egghead](https://egghead.io/playlists/getting-started-with-svelte-3-05a8541a) +- [Udemy](https://www.udemy.com/sveltejs-the-complete-guide/) + +Note that Udemy very frequently has discounts over 90%. diff --git a/site/content/faq/300-is-svelte-dev-down.md b/site/content/faq/300-is-svelte-dev-down.md new file mode 100644 index 0000000000..a7ce9984b7 --- /dev/null +++ b/site/content/faq/300-is-svelte-dev-down.md @@ -0,0 +1,5 @@ +--- +question: Is svelte.dev down? +--- + +Probably not, but it's possible. If you can't seem to access any `.dev` sites, check out [this SuperUser question and answer](https://superuser.com/q/1413402). \ No newline at end of file diff --git a/site/content/faq/400-how-can-i-get-syntax-highlighting.md b/site/content/faq/400-how-can-i-get-syntax-highlighting.md new file mode 100644 index 0000000000..90f10b254f --- /dev/null +++ b/site/content/faq/400-how-can-i-get-syntax-highlighting.md @@ -0,0 +1,5 @@ +--- +question: How can I get VSCode to syntax-highlight my .svelte files? +--- + +There is an [official VSCode extension for Svelte](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode), however it is still in the **beta** testing stage, and not all issues have been ironed out. \ No newline at end of file diff --git a/site/content/faq/500-what-about-typescript-support.md b/site/content/faq/500-what-about-typescript-support.md new file mode 100644 index 0000000000..dae27143ed --- /dev/null +++ b/site/content/faq/500-what-about-typescript-support.md @@ -0,0 +1,5 @@ +--- +question: What about Typescript support? +--- + +You need to install a [community supported preprocessor](https://github.com/sveltejs/integrations#preprocessors) such as [svelte-preprocess](https://github.com/kaisermann/svelte-preprocess). Work is ongoing to improve [IDE support](https://github.com/sveltejs/language-tools/issues/83) and build [additional CLI tooling](https://github.com/sveltejs/language-tools/issues/68) \ No newline at end of file diff --git a/site/content/faq/600-does-svelte-scale.md b/site/content/faq/600-does-svelte-scale.md new file mode 100644 index 0000000000..06c9b58a97 --- /dev/null +++ b/site/content/faq/600-does-svelte-scale.md @@ -0,0 +1,5 @@ +--- +question: Does Svelte scale? +--- + +There will be a blog post about this eventually, but in the meantime, check out [this issue](https://github.com/sveltejs/svelte/issues/2546). diff --git a/site/content/faq/700-is-there-a-component-library.md b/site/content/faq/700-is-there-a-component-library.md new file mode 100644 index 0000000000..fd771eabda --- /dev/null +++ b/site/content/faq/700-is-there-a-component-library.md @@ -0,0 +1,5 @@ +--- +question: Is there a UI component library? +--- + +There are several UI component libraries. Find them under the [code section](https://svelte-community.netlify.com/code) of the Svelte Community website. diff --git a/site/content/faq/800-how-do-i-test-svelte-apps.md b/site/content/faq/800-how-do-i-test-svelte-apps.md new file mode 100644 index 0000000000..af67bebf3f --- /dev/null +++ b/site/content/faq/800-how-do-i-test-svelte-apps.md @@ -0,0 +1,6 @@ +--- +question: How do I test Svelte apps? +--- + +We don't have a good answer to this yet, but it is a priority. There are a few approaches that people take when testing, but it generally involves compiling the component and mounting it to something and then performing the tests. +You essentially need to create a bundle for each component you're testing (since svelte is a compiler and not a normal library) and then mount them. You can mount to a JSDOM instance, or you can use Puppeteer if you need a real browser, or you can use a tool like Cypress. There is an example of this in the Sapper starter template. diff --git a/site/content/faq/900-is-there-a-router.md b/site/content/faq/900-is-there-a-router.md new file mode 100644 index 0000000000..a78c478943 --- /dev/null +++ b/site/content/faq/900-is-there-a-router.md @@ -0,0 +1,13 @@ +--- +question: Is there a router? +--- + +You can use any router lib you want. A lot of people use [page.js](https://github.com/visionmedia/page.js). There's also [navaid](https://github.com/lukeed/navaid), which is very similar. + +If you prefer a declarative HTML approach, there's [svelte-routing](https://github.com/EmilTholin/svelte-routing). + +If you need hash-based routing on the client side, check out [svelte-spa-router](https://github.com/ItalyPaleAle/svelte-spa-router), or [abstract-state-router](https://github.com/TehShrike/abstract-state-router/), a mature router for business software. + +For filesystem-based routing, you can take a look at [Routify](https://routify.dev). + +For an official solution, there's nothing that's simply a routing library. There is, however, the official [Sapper](https://sapper.svelte.dev/) framework, a Next.js-style application framework built on Svelte, which includes its own filesystem-based routing. \ No newline at end of file diff --git a/site/src/routes/blog/[slug].svelte b/site/src/routes/blog/[slug].svelte index 610fb6506b..d5fd6c0676 100644 --- a/site/src/routes/blog/[slug].svelte +++ b/site/src/routes/blog/[slug].svelte @@ -133,44 +133,14 @@ border: 0.8rem solid var(--second); } - /* headers anchors */ - - .post :global(.offset-anchor) { - position: relative; - display: block; - top: calc(-1 * (var(--nav-h) + var(--top-offset) - 1rem)); - width: 0; - height: 0; - } - .post :global(.anchor) { - position: absolute; - display: block; - background: url(/icons/link.svg) 0 50% no-repeat; - background-size: 1em 1em; - width: 1.4em; - height: 1em; top: calc((var(--h3) - 24px) / 2); - left: -1.4em; - opacity: 0; - transition: opacity 0.2s; - border: none !important; /* TODO get rid of linkify */ } - .post :global(h2):hover :global(.anchor), - .post :global(h3):hover :global(.anchor), - .post :global(h4):hover :global(.anchor), - .post :global(h5):hover :global(.anchor), - .post :global(h6):hover :global(.anchor) { - opacity: 1; - } - - @media (max-width: 768px) { .post :global(.anchor) { transform: scale(0.6); opacity: 1; - top: calc((1em - 0.6 * 24px) / 2); left: -1.0em; } } diff --git a/site/src/routes/faq.js b/site/src/routes/faq.js deleted file mode 100644 index 6263494a4c..0000000000 --- a/site/src/routes/faq.js +++ /dev/null @@ -1,4 +0,0 @@ -export function get(req, res) { - res.writeHead(302, { Location: 'https://github.com/sveltejs/svelte/wiki/FAQ' }); - res.end(); -} \ No newline at end of file diff --git a/site/src/routes/faq/_faqs.js b/site/src/routes/faq/_faqs.js new file mode 100644 index 0000000000..fa48e02282 --- /dev/null +++ b/site/src/routes/faq/_faqs.js @@ -0,0 +1,58 @@ +import fs from 'fs'; +import path from 'path'; +import { extract_frontmatter, link_renderer } from '@sveltejs/site-kit/utils/markdown.js'; +import marked from 'marked'; +import { makeSlugProcessor } from '../../utils/slug'; +import { highlight } from '../../utils/highlight'; +import { SLUG_PRESERVE_UNICODE } from '../../../config'; + +const makeSlug = makeSlugProcessor(SLUG_PRESERVE_UNICODE); + +export default function get_faqs() { + return fs + .readdirSync('content/faq') + .map(file => { + if (path.extname(file) !== '.md') return; + + const match = /^([0-9]+)-(.+)\.md$/.exec(file); + if (!match) throw new Error(`Invalid filename '${file}'`); + + const [, order, slug] = match; + + const markdown = fs.readFileSync(`content/faq/${file}`, 'utf-8'); + + const { content, metadata } = extract_frontmatter(markdown); + + const renderer = new marked.Renderer(); + + renderer.link = link_renderer; + + renderer.code = highlight; + + renderer.heading = (text, level, rawtext) => { + const fragment = makeSlug(rawtext); + + return ` + + +
    + ${text} + `; + }; + + const answer = marked( + content.replace(/^\t+/gm, match => match.split('\t').join(' ')), + { renderer } + ); + + const fragment = makeSlug(slug); + + return { + fragment, + order, + answer, + metadata + }; + }) + .sort((a, b) => a.order - b.order); +} diff --git a/site/src/routes/faq/index.json.js b/site/src/routes/faq/index.json.js new file mode 100644 index 0000000000..b6810a984e --- /dev/null +++ b/site/src/routes/faq/index.json.js @@ -0,0 +1,24 @@ +import send from '@polka/send'; +import get_faqs from './_faqs.js'; + +let json; + +export function get(req, res) { + if (!json || process.env.NODE_ENV !== 'production') { + const faqs = get_faqs() + .map(faq => { + return { + fragment: faq.fragment, + answer: faq.answer, + metadata: faq.metadata + }; + }); + + json = JSON.stringify(faqs); + } + + send(res, 200, json, { + 'Content-Type': 'application/json', + 'Cache-Control': `max-age=${5 * 60 * 1e3}` // 5 minutes + }); +} diff --git a/site/src/routes/faq/index.svelte b/site/src/routes/faq/index.svelte new file mode 100644 index 0000000000..c426f5bf3d --- /dev/null +++ b/site/src/routes/faq/index.svelte @@ -0,0 +1,89 @@ + + + + + + Frequently Asked Questions • Svelte + + + + + + +
    +

    Frequently Asked Questions

    + {#each faqs as faq} + +
    +

    + +   + {faq.metadata.question} +

    +

    {@html faq.answer}

    +
    + {/each} +
    + + diff --git a/site/src/template.html b/site/src/template.html index 344684d766..28ea1910a5 100644 --- a/site/src/template.html +++ b/site/src/template.html @@ -7,6 +7,7 @@ %sapper.base% + diff --git a/site/static/global.css b/site/static/global.css index 14e4e0a0c0..7aa0715a98 100644 --- a/site/static/global.css +++ b/site/static/global.css @@ -1,500 +1,31 @@ -/* ------------------------------------------------ - vars – css custom-properties +/* headers anchors */ - NOTE - - some vars change inside media-queries! - - under normal conditions, there's no need to touch these ------------------------------------------------ -*/ -:root { - --nav-h: 6rem; - --top-offset: 6rem; - --sidebar-w: 30rem; - --sidebar-mid-w: 36rem; - --sidebar-large-w: 48rem; - --main-width: 80rem; - --code-w: 72em; - --side-nav: 3.2rem; - --side-page: var(--side-nav); - - /* easings */ - --in-cubic: cubic-bezier(0.55, 0.055, 0.675, 0.19); - --out-cubic: cubic-bezier(0.215, 0.61, 0.355, 1); - --inout-cubic: cubic-bezier(0.645, 0.045, 0.355, 1); - - --in-back: cubic-bezier(0.6, -0.28, 0.735, 0.045); - --out-back: cubic-bezier(0.175, 0.885, 0.32, 1.275); - --inout-back: cubic-bezier(0.68, -0.55, 0.265, 1.55); -} - -@media screen and (min-width: 768px) { - :root { - --side-page: 14vw; - --top-offset: 10rem; - --side-nav: 4.8rem; - } -} - -/* theme vars */ -.theme-default { - --back: #ffffff; - --back-light: #f6fafd; - --back-api: #eff8ff; - --prime: #ff3e00; - --second: #676778; - --flash: #40b3ff; - --heading: var(--second); - --text: #444; - --sidebar-text: rgba(255, 255, 255, .75); - --border-w: .3rem; /* border-width */ - --border-r: .4rem; /* border-radius */ -} - -/* typo vars */ -.typo-default { - --unit: .8rem; - --code-fs: 1.3rem; - --h6: 1.4rem; - --h5: 1.6rem; - --h4: 1.8rem; /* default font-size */ - --h3: 2.6rem; - --h2: 3rem; - --h1: 3.2rem; - --linemax: 42em; /* max line-length */ - --lh: 1.5; /* base line-height */ -} - -body { - --font: 'Overpass', sans-serif; - --font-mono: 'Fira Mono', monospace; - --font-ui: var(--font-mono); - --font-system: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif; -} - - -/* fonts ---------------------------------- */ -/* overpass-300normal - latin */ -@font-face { - font-family: 'Overpass'; - font-style: normal; - font-weight: 300; - font-display: fallback; - src: - local('Overpass Light '), - local('Overpass-Light'), - url('fonts/overpass/overpass-latin-300.woff2') format('woff2'); -} - -/* overpass-600normal - latin */ -@font-face { - font-family: 'Overpass'; - font-style: normal; - font-weight: 600; - font-display: fallback; - src: - local('Overpass Bold '), - local('Overpass-Bold'), - url('fonts/overpass/overpass-latin-600.woff2') format('woff2'); -} - -/* fira-mono-400normal - latin */ -@font-face { - font-family: 'Fira Mono'; - font-style: normal; - font-weight: 400; - font-display: fallback; - src: - local('Fira Mono Regular '), - local('Fira Mono-Regular'), - url('fonts/fira-mono/fira-mono-latin-400.woff2') format('woff2'); -} - -/* base reset ----------------------------- */ -html { - font-size: 62.5%; - -ms-text-size-adjust: 62.5%; - -webkit-text-size-adjust: 62.5%; - -ms-overflow-style: -ms-autohiding-scrollbar; - box-sizing: border-box; - border-collapse: collapse; -} - -html, -body, -#sapper { - width: 100%; - height: 100%; -} - -* { - box-sizing: inherit; - margin: 0; - padding: 0; -} - -/* link reset ----------------------------- */ -a { - text-decoration: none; - cursor: pointer; - color: inherit; -} - -a:hover, a:active { color: var(--flash) } -a:focus { outline: none } - -/* ------------------------------------------------ - global styles ------------------------------------------------ -*/ - -/* typography ----------------------------- */ -body { - font: 300 var(--h4)/var(--lh) var(--font); - background-color: var(--back); - color: var(--text); - - /* default spacing of Overpass is a bit too airy */ - /* letter-spacing: -.013em; */ -} - -h1, h2, h3, h4, h5, h6, blockquote { - position: relative; - margin: 0; - color: var(--heading); -} - -/* h1, h2, h3, h4, h5, h6 { font-weight: 600 } */ -h6 { font-size: var(--h6) } -h5 { font-size: var(--h5) } -h4 { font-size: var(--h4) } -h3 { font-size: var(--h3) } -h2 { font-size: var(--h2) } -h1 { font-size: var(--h1) } - -h1, h2 { - font-family: var(--font); - line-height: 1.25; -} - -h3 { font-weight: 300 } - -p, ol, ul { - margin: 0 0 1em 0; -} - -.b, b, strong { font-weight: 600 } - -tt, code, kbd, samp { - font: 400 var(--code-fs)/1.7 var(--font-mono); -} - -code { - position: relative; - border-radius: .3em; - white-space: nowrap; - color: #444; - -webkit-font-smoothing: initial; -} - -pre code { - top: 0; - white-space: inherit; - background-color: none; -} - -/* sync CodeMirror with prism */ -.CodeMirror { - font-size: var(--code-fs) !important; -} - -::selection { - background: var(--flash); - color: white; -} - -/* opinionated styles --------------------- */ - -li:not(.white) > h2 { - color: var(--second) -} - -blockquote { - position: relative; - margin: 1.6rem 0 2.4rem; - padding: 2rem 2.4rem 1.8rem 2.4rem; - border-radius: var(--border-r); - font-family: var(--font); - max-width: var(--linemax); -} - -blockquote p { - font-size: var(--h5); -} - -blockquote :last-child { - margin: 0; -} - -/* buttons -------------------------------- */ -button { - font-family: inherit; - font-size: inherit; - background-color: transparent; - border: none; - color: currentColor; - cursor: pointer; -} - -button:focus, -.btn:focus { outline: 0 } - -button[disabled], -.btn[disabled], -.btn:hover[disabled] { - opacity: .55; - pointer-events: none; -} - -button > svg, -.btn > svg { - position: relative; - top: -.1rem; - width: 2rem !important; - height: 2rem !important; - stroke: currentColor !important; -} - -/* reset ------- */ -.btn { - --btn-h: 4rem; - --btn-outline: .2rem; - --btn-font: var(--font); - --btn-calc-h: calc(var(--btn-h) - var(--btn-outline) * 2); - --btn-hover: linear-gradient(to top, rgba(0,0,0,.07), rgba(0,0,0,.07)); - - position: relative; - margin: 0 .8rem .8rem 0; - vertical-align: middle; - white-space: nowrap; - display: inline-block; - zoom: 1; - border: none transparent; - font: var(--h4) var(--btn-font); - border-radius: var(--border-r); - color: currentColor; - cursor: pointer; -} - -/* default */ -.btn { - line-height: var(--btn-h); - height: var(--btn-h); - padding: 0 1.6rem; - transition: all .1s; -} - -.btn:hover { - transform: scale(.98); - mix-blend-mode: multiply; - background-image: var(--btn-hover); -} - -/* optional */ -.btn[outline] { - line-height: var(--btn-calc-h); - height: var(--btn-calc-h); - border: var(--btn-outline) solid currentColor; - background-color: white; - color: currentColor; -} - -/* links ------------------------------------- */ -a { - position: relative; - padding: 0 0 1px 0; - border-bottom: 1px solid currentColor; - user-select: none; - color: var(--prime); - transition: color .2s, border .2s, padding .2s; -} - -a:hover { - color: var(--flash); -} - -a:hover { - padding: 0; - border-bottom: 2px solid currentColor; -} - -a.no-underline { - border-bottom: none; - padding: 0; -} - -/* a:hover:not(.disabled) > .icon { stroke: var(--flash) } */ - -/* lists ---------------------------------- */ -.listify ol, -.listify ul { - --list-padding: 2.9rem; - - list-style: none; - color: currentColor; - margin-left: var(--list-padding); -} - -.listify ol > li, -.listify ul > li { - max-width: calc(var(--linemax) - var(--list-padding)); - line-height: 1.5; - margin: 0 0 0.4rem 0; -} - -.listify ul > li:before { - content: ''; - position: absolute; - margin-top: 1.1rem; - margin-left: -1.8rem; - background-color: var(--second); - width: .6rem; - height: .6rem; - border-radius: 2px; - opacity: 0.7; -} - -.listify ol { list-style: decimal } - -/* tables --------------------------------- */ -table { - width: 100%; - font-size: var(--h5); -} - -td, th { - text-align: left; - border-bottom: 1px solid #eee; - padding: 0.4rem 0.8rem 0.4rem 0; -} - -table code, table span { - white-space: pre; -} - -/* grid ----------------------------------- */ -.grid, .grid.half { - display: grid; - grid-gap: 2.4rem; - grid-template-columns: 1fr; - align-items: center; -} - -.grid.stretch { align-items: stretch } - -.grid > .cols-2, -.grid > .cols-3 { grid-column: span 1 } - -@media screen and (min-width: 840px) { - .grid.half, - .grid { grid-template-columns: repeat(2, 1fr) } - .grid > .cols-2, - .grid > .cols-3 { grid-column: span 2 } -} - -@media screen and (min-width: 1100px) { - .grid { grid-template-columns: repeat(3, 1fr) } - .grid > .cols-2 { grid-column: span 2 } - .grid > .cols-3 { grid-column: span 3 } -} - -/* helper styles -------------------------- */ -.flex-auto { flex: 1 0 auto } - -.py0 { - padding-top: 0 !important; - padding-bottom: 0 !important; -} - -.legend, figcaption, .post aside { - max-width: none; - margin: 0 auto; - padding: 1.6rem 0 0 .8rem; - font: 1.2rem/1.6 var(--font-ui); -} - -.filename { - display: inline-block; - padding: 1.6rem 0 0 1rem; - font: var(--h6) var(--font-ui); -} - -.box { - padding: 2.4rem 3.2rem; - border-radius: var(--border-r); -} - -/* theme colors --------------------------- */ -.prime { color: var(--prime) !important } -.second { color: var(--second) !important } -.flash { color: var(--flash) !important } -.black { color: black !important } -.white { color: white !important } - -.back { background-color: var(--back) !important } -.back-light { background-color: var(--back-light) !important } -.bg-prime { background-color: var(--prime) !important } -.bg-second { background-color: var(--second) !important } -.bg-flash { background-color: var(--flash) !important } - -/* inputs --------------------------------- */ -input[type="checkbox"] { - /* display: block; */ +.offset-anchor { position: relative; - height: 1em; - width: calc(100% - 0.6em); - max-width: 2em; - top: -2px; - border-radius: 0.5em; - -webkit-appearance: none; - outline: none; - margin: 0 0.6em 0 0; -} - -input[type="checkbox"]::before { - content: ""; - position: absolute; display: block; - height: 100%; - width: 100%; - padding: 2px; - border-radius: 1em; - top: 0; - left: 0; - background: var(--second); - /* box-sizing: border-box; */ - box-sizing: content-box; + top: calc(-1 * (var(--nav-h) + var(--top-offset)) + 11rem); + width: 0; + height: 0; } -input[type="checkbox"]:checked::before { - background: var(--prime); -} - -input[type="checkbox"]::after { - content: ""; +.anchor { position: absolute; display: block; + background: url(/icons/link.svg) 0 50% no-repeat; + background-size: 1em 1em; + width: 1.4em; height: 1em; - width: 1em; - top: 2px; - left: 2px; - border-radius: 1em; - background: white; - box-shadow: 0 0px 1px rgba(0,0,0,.4), 0 4px 2px rgba(0,0,0,.1); - -webkit-transition: background .2s ease-out, left .2s ease-out; -} - -input[type="checkbox"]:checked::after { - left: calc(100% - 9px); + top: calc(((var(--h3) - 24px) / 2) + 0.6em); + left: -1.4em; + opacity: 0; + transition: opacity 0.2s; + border: none !important; /* TODO get rid of linkify */ +} + +h2:hover .anchor, +h3:hover .anchor, +h4:hover .anchor, +h5:hover .anchor, +h6:hover .anchor { + opacity: 1; } From 5ac8a1d07a86ff5d87dfffe2c3f82011071fc051 Mon Sep 17 00:00:00 2001 From: Ben McCann <322311+benmccann@users.noreply.github.com> Date: Wed, 13 May 2020 04:53:06 -0700 Subject: [PATCH 54/69] docs: link to list of community preprocessors (#4817) --- site/content/docs/04-compile-time.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/site/content/docs/04-compile-time.md b/site/content/docs/04-compile-time.md index f9bfa772fe..9fa0f92195 100644 --- a/site/content/docs/04-compile-time.md +++ b/site/content/docs/04-compile-time.md @@ -183,6 +183,10 @@ const ast = svelte.parse(source, { filename: 'App.svelte' }); ### `svelte.preprocess` +A number of [community-maintained preprocessing plugins](https://github.com/sveltejs/integrations#preprocessors) are available to allow you to use Svelte with tools like TypeScript, PostCSS, SCSS, and Less. + +You can write your own preprocessor using the `svelte.preprocess` API. + ```js result: { code: string, From c9020d35b7ca52b381a9afd9d50206ba42fc13bc Mon Sep 17 00:00:00 2001 From: Conduitry Date: Fri, 15 May 2020 06:19:56 -0400 Subject: [PATCH 55/69] site: bump @sveltejs/svelte-repl --- site/package-lock.json | 12 ++++++------ site/package.json | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/site/package-lock.json b/site/package-lock.json index 68fcf1f289..972f8e7ef6 100644 --- a/site/package-lock.json +++ b/site/package-lock.json @@ -1291,9 +1291,9 @@ } }, "@sveltejs/svelte-repl": { - "version": "0.1.18", - "resolved": "https://registry.npmjs.org/@sveltejs/svelte-repl/-/svelte-repl-0.1.18.tgz", - "integrity": "sha512-b1psPdlJ9qRqn28l+sPSma6OSfilue0dGK4BcS75MOsfdjZjSvsc0AIAoriQ3q1mB/frCoyUri6nxFGgEKv0iQ==", + "version": "0.1.19", + "resolved": "https://registry.npmjs.org/@sveltejs/svelte-repl/-/svelte-repl-0.1.19.tgz", + "integrity": "sha512-35R94X6uYgy6PHLQnQCsKcZ4zb6rGGQXjBYqjuCkoCykIlSLx8/avq6BGqudmE5pzVWDP3kk4063cHgccy1xYg==", "dev": true, "requires": { "codemirror": "^5.49.2", @@ -1601,9 +1601,9 @@ "dev": true }, "codemirror": { - "version": "5.52.2", - "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.52.2.tgz", - "integrity": "sha512-WCGCixNUck2HGvY8/ZNI1jYfxPG5cRHv0VjmWuNzbtCLz8qYA5d+je4QhSSCtCaagyeOwMi/HmmPTjBgiTm2lQ==", + "version": "5.53.2", + "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.53.2.tgz", + "integrity": "sha512-wvSQKS4E+P8Fxn/AQ+tQtJnF1qH5UOlxtugFLpubEZ5jcdH2iXTVinb+Xc/4QjshuOxRm4fUsU2QPF1JJKiyXA==", "dev": true }, "color-convert": { diff --git a/site/package.json b/site/package.json index 057d9f9adf..5333acece4 100644 --- a/site/package.json +++ b/site/package.json @@ -36,7 +36,7 @@ "@babel/runtime": "^7.6.0", "@sindresorhus/slugify": "^0.9.1", "@sveltejs/site-kit": "^1.1.4", - "@sveltejs/svelte-repl": "^0.1.18", + "@sveltejs/svelte-repl": "^0.1.19", "degit": "^2.1.4", "dotenv": "^8.1.0", "esm": "^3.2.25", From 81ade59797a25139a811c3cd8bebecf282953a78 Mon Sep 17 00:00:00 2001 From: Daniel Imfeld Date: Sat, 16 May 2020 22:26:35 -1000 Subject: [PATCH 56/69] fix check for uninitialized `condition` (#4841) --- src/compiler/compile/render_dom/wrappers/IfBlock.ts | 2 +- .../RRR.svelte | 1 + .../_config.js | 3 +++ .../main.svelte | 13 +++++++++++++ 4 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 test/runtime/samples/if-block-static-with-elseif-else-and-outros/RRR.svelte create mode 100644 test/runtime/samples/if-block-static-with-elseif-else-and-outros/_config.js create mode 100644 test/runtime/samples/if-block-static-with-elseif-else-and-outros/main.svelte diff --git a/src/compiler/compile/render_dom/wrappers/IfBlock.ts b/src/compiler/compile/render_dom/wrappers/IfBlock.ts index e18d9f3b6b..220b529902 100644 --- a/src/compiler/compile/render_dom/wrappers/IfBlock.ts +++ b/src/compiler/compile/render_dom/wrappers/IfBlock.ts @@ -392,7 +392,7 @@ export default class IfBlockWrapper extends Wrapper { ${snippet && ( dependencies.length > 0 ? b`if (${block.renderer.dirty(dependencies)}) ${condition} = !!${snippet}` - : b`if (${condition} == -1) ${condition} = !!${snippet}` + : b`if (${condition} == null) ${condition} = !!${snippet}` )} if (${condition}) return ${i};` : b`return ${i};`)} diff --git a/test/runtime/samples/if-block-static-with-elseif-else-and-outros/RRR.svelte b/test/runtime/samples/if-block-static-with-elseif-else-and-outros/RRR.svelte new file mode 100644 index 0000000000..7242373249 --- /dev/null +++ b/test/runtime/samples/if-block-static-with-elseif-else-and-outros/RRR.svelte @@ -0,0 +1 @@ +rrr \ No newline at end of file diff --git a/test/runtime/samples/if-block-static-with-elseif-else-and-outros/_config.js b/test/runtime/samples/if-block-static-with-elseif-else-and-outros/_config.js new file mode 100644 index 0000000000..8b2c6d2d66 --- /dev/null +++ b/test/runtime/samples/if-block-static-with-elseif-else-and-outros/_config.js @@ -0,0 +1,3 @@ +export default { + html: 'eee' +}; diff --git a/test/runtime/samples/if-block-static-with-elseif-else-and-outros/main.svelte b/test/runtime/samples/if-block-static-with-elseif-else-and-outros/main.svelte new file mode 100644 index 0000000000..b60885722e --- /dev/null +++ b/test/runtime/samples/if-block-static-with-elseif-else-and-outros/main.svelte @@ -0,0 +1,13 @@ + + +{#if "Eva".startsWith('E')} + eee +{:else if x} + def +{:else} + +{/if} From 283f9ae6cf774332e9c5934fedd1fb9bb432596c Mon Sep 17 00:00:00 2001 From: Conduitry Date: Sun, 17 May 2020 04:29:29 -0400 Subject: [PATCH 57/69] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3743f2d340..d3371bb607 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * Support default values and trailing commas in destructuring `{#await}` ([#4560](https://github.com/sveltejs/svelte/issues/4560), [#4810](https://github.com/sveltejs/svelte/issues/4810)) * Fix setting `value` attribute with `bind:group` and attribute spread ([#4803](https://github.com/sveltejs/svelte/issues/4803)) +* Fix issue with compound `{#if}` block involving static condition, dynamic condition, and inline component ([#4840](https://github.com/sveltejs/svelte/issues/4840)) ## 3.22.2 From ef2a886c8397a602543d3fa5bbc9bcc2aefca55c Mon Sep 17 00:00:00 2001 From: Pat Cavit Date: Sun, 17 May 2020 01:46:13 -0700 Subject: [PATCH 58/69] fix: cache value & cancel tween for instant-set (#4847) --- src/runtime/motion/tweened.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/runtime/motion/tweened.ts b/src/runtime/motion/tweened.ts index abbb3b1aa2..c802604c0e 100644 --- a/src/runtime/motion/tweened.ts +++ b/src/runtime/motion/tweened.ts @@ -94,7 +94,12 @@ export function tweened(value?: T, defaults: Options = {}): Tweened { } = assign(assign({}, defaults), opts); if (duration === 0) { - store.set(target_value); + if (previous_task) { + previous_task.abort(); + previous_task = null; + } + + store.set(value = target_value); return Promise.resolve(); } From bdbc73f111ccfdd27e748bc5cd73ed6af6e035f2 Mon Sep 17 00:00:00 2001 From: Conduitry Date: Sun, 17 May 2020 04:47:54 -0400 Subject: [PATCH 59/69] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d3371bb607..1ead5c723d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased * Support default values and trailing commas in destructuring `{#await}` ([#4560](https://github.com/sveltejs/svelte/issues/4560), [#4810](https://github.com/sveltejs/svelte/issues/4810)) +* Fix handling of `tweened` store when set using `duration: 0` ([#4799](https://github.com/sveltejs/svelte/issues/4799), [#4846](https://github.com/sveltejs/svelte/issues/4846)) * Fix setting `value` attribute with `bind:group` and attribute spread ([#4803](https://github.com/sveltejs/svelte/issues/4803)) * Fix issue with compound `{#if}` block involving static condition, dynamic condition, and inline component ([#4840](https://github.com/sveltejs/svelte/issues/4840)) From a9fd168da3f156097221b7fa18de7d98df91310c Mon Sep 17 00:00:00 2001 From: Billy Levin Date: Sun, 17 May 2020 09:51:17 +0100 Subject: [PATCH 60/69] update for ARIA 1.2 working draft (#4845) --- src/compiler/compile/nodes/Element.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compiler/compile/nodes/Element.ts b/src/compiler/compile/nodes/Element.ts index a337eff73a..7adf46076d 100644 --- a/src/compiler/compile/nodes/Element.ts +++ b/src/compiler/compile/nodes/Element.ts @@ -19,10 +19,10 @@ import { INode } from './interfaces'; const svg = /^(?:altGlyph|altGlyphDef|altGlyphItem|animate|animateColor|animateMotion|animateTransform|circle|clipPath|color-profile|cursor|defs|desc|discard|ellipse|feBlend|feColorMatrix|feComponentTransfer|feComposite|feConvolveMatrix|feDiffuseLighting|feDisplacementMap|feDistantLight|feDropShadow|feFlood|feFuncA|feFuncB|feFuncG|feFuncR|feGaussianBlur|feImage|feMerge|feMergeNode|feMorphology|feOffset|fePointLight|feSpecularLighting|feSpotLight|feTile|feTurbulence|filter|font|font-face|font-face-format|font-face-name|font-face-src|font-face-uri|foreignObject|g|glyph|glyphRef|hatch|hatchpath|hkern|image|line|linearGradient|marker|mask|mesh|meshgradient|meshpatch|meshrow|metadata|missing-glyph|mpath|path|pattern|polygon|polyline|radialGradient|rect|set|solidcolor|stop|svg|switch|symbol|text|textPath|tref|tspan|unknown|use|view|vkern)$/; -const aria_attributes = 'activedescendant atomic autocomplete busy checked colindex controls current describedby details disabled dropeffect errormessage expanded flowto grabbed haspopup hidden invalid keyshortcuts label labelledby level live modal multiline multiselectable orientation owns placeholder posinset pressed readonly relevant required roledescription rowindex selected setsize sort valuemax valuemin valuenow valuetext'.split(' '); +const aria_attributes = 'activedescendant atomic autocomplete busy checked colcount colindex colspan controls current describedby details disabled dropeffect errormessage expanded flowto grabbed haspopup hidden invalid keyshortcuts label labelledby level live modal multiline multiselectable orientation owns placeholder posinset pressed readonly relevant required roledescription rowcount rowindex rowspan selected setsize sort valuemax valuemin valuenow valuetext'.split(' '); const aria_attribute_set = new Set(aria_attributes); -const aria_roles = 'alert alertdialog application article banner button cell checkbox columnheader combobox command complementary composite contentinfo definition dialog directory document feed figure form grid gridcell group heading img input landmark link list listbox listitem log main marquee math menu menubar menuitem menuitemcheckbox menuitemradio navigation none note option presentation progressbar radio radiogroup range region roletype row rowgroup rowheader scrollbar search searchbox section sectionhead select separator slider spinbutton status structure switch tab table tablist tabpanel term textbox timer toolbar tooltip tree treegrid treeitem widget window'.split(' '); +const aria_roles = 'alert alertdialog application article banner blockquote button caption cell checkbox code columnheader combobox complementary contentinfo definition deletion dialog directory document emphasis feed figure form generic grid gridcell group heading img link list listbox listitem log main marquee math meter menu menubar menuitem menuitemcheckbox menuitemradio navigation none note option paragraph presentation progressbar radio radiogroup region row rowgroup rowheader scrollbar search searchbox separator slider spinbutton status strong subscript superscript switch tab table tablist tabpanel term textbox time timer toolbar tooltip tree treegrid treeitem'.split(' '); const aria_role_set = new Set(aria_roles); const a11y_required_attributes = { From a1b0295fc395a3742717c1abfafcb8022d3897ff Mon Sep 17 00:00:00 2001 From: Conduitry Date: Sun, 17 May 2020 04:52:42 -0400 Subject: [PATCH 61/69] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ead5c723d..058f6e29ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ * Fix handling of `tweened` store when set using `duration: 0` ([#4799](https://github.com/sveltejs/svelte/issues/4799), [#4846](https://github.com/sveltejs/svelte/issues/4846)) * Fix setting `value` attribute with `bind:group` and attribute spread ([#4803](https://github.com/sveltejs/svelte/issues/4803)) * Fix issue with compound `{#if}` block involving static condition, dynamic condition, and inline component ([#4840](https://github.com/sveltejs/svelte/issues/4840)) +* Update a11y warnings per ARIA 1.2 working draft ([#4844](https://github.com/sveltejs/svelte/issues/4844)) ## 3.22.2 From d8fb0bb62c27d2cbb4097ca25953258813c158a7 Mon Sep 17 00:00:00 2001 From: Jeremy Bernier Date: Sun, 17 May 2020 18:43:39 +0900 Subject: [PATCH 62/69] Fix input with bind:value displaying "undefined" There's a bug where when `` and `sampleVar` is updated to undefined (`sampleVar = undefined`), then the input displays "undefined" (despite the input initally showing up correctly empty when initialized to undefined). This issue has been documented since September 14, 2019 https://github.com/sveltejs/svelte/issues/3569 I'm new to Svelte, but this seems to fix this problem. --- src/runtime/internal/dom.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/runtime/internal/dom.ts b/src/runtime/internal/dom.ts index e447eae362..a502cdb6f5 100644 --- a/src/runtime/internal/dom.ts +++ b/src/runtime/internal/dom.ts @@ -193,7 +193,7 @@ export function set_data(text, data) { export function set_input_value(input, value) { if (value != null || input.value) { - input.value = value; + input.value = value || ''; } } From 33d8979495e483c769505657c6c981ef869f670f Mon Sep 17 00:00:00 2001 From: Conduitry Date: Sun, 17 May 2020 09:46:54 -0400 Subject: [PATCH 63/69] -> v3.22.3 --- CHANGELOG.md | 2 +- package-lock.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 058f6e29ef..5918d2b7d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Svelte changelog -## Unreleased +## 3.22.3 * Support default values and trailing commas in destructuring `{#await}` ([#4560](https://github.com/sveltejs/svelte/issues/4560), [#4810](https://github.com/sveltejs/svelte/issues/4810)) * Fix handling of `tweened` store when set using `duration: 0` ([#4799](https://github.com/sveltejs/svelte/issues/4799), [#4846](https://github.com/sveltejs/svelte/issues/4846)) diff --git a/package-lock.json b/package-lock.json index 937f04f5f4..06c562e448 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "svelte", - "version": "3.22.2", + "version": "3.22.3", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 91bf708676..ce4ee88ad3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "svelte", - "version": "3.22.2", + "version": "3.22.3", "description": "Cybernetically enhanced web apps", "module": "index.mjs", "main": "index", From 6397588e50196aa006581d3fffb65a39309c8248 Mon Sep 17 00:00:00 2001 From: Richard Harris Date: Sun, 17 May 2020 11:19:58 -0400 Subject: [PATCH 64/69] add test for #3569 and similar situations --- .../binding-input-text-undefined/_config.js | 30 +++++++++++++++++++ .../binding-input-text-undefined/main.svelte | 5 ++++ 2 files changed, 35 insertions(+) create mode 100644 test/runtime/samples/binding-input-text-undefined/_config.js create mode 100644 test/runtime/samples/binding-input-text-undefined/main.svelte diff --git a/test/runtime/samples/binding-input-text-undefined/_config.js b/test/runtime/samples/binding-input-text-undefined/_config.js new file mode 100644 index 0000000000..009e1b37ba --- /dev/null +++ b/test/runtime/samples/binding-input-text-undefined/_config.js @@ -0,0 +1,30 @@ +export default { + html: ` + + `, + + ssrHtml: ` + + `, + + async test({ assert, component, target, window }) { + const input = target.querySelector('input'); + assert.equal(input.value, ''); + + component.x = null; + assert.equal(input.value, ''); + + component.x = undefined; + assert.equal(input.value, ''); + + component.x = 'string'; + component.x = undefined; + assert.equal(input.value, ''); + + component.x = 0; + assert.equal(input.value, '0'); + + component.x = undefined; + assert.equal(input.value, ''); + }, +}; diff --git a/test/runtime/samples/binding-input-text-undefined/main.svelte b/test/runtime/samples/binding-input-text-undefined/main.svelte new file mode 100644 index 0000000000..b4c2a84fd2 --- /dev/null +++ b/test/runtime/samples/binding-input-text-undefined/main.svelte @@ -0,0 +1,5 @@ + + + \ No newline at end of file From 081f7cd878a4624cf5c74570ad281193f2434f43 Mon Sep 17 00:00:00 2001 From: Richard Harris Date: Sun, 17 May 2020 11:20:18 -0400 Subject: [PATCH 65/69] dont coerce all falsy values to empty strings --- src/runtime/internal/dom.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/runtime/internal/dom.ts b/src/runtime/internal/dom.ts index a502cdb6f5..1823cba273 100644 --- a/src/runtime/internal/dom.ts +++ b/src/runtime/internal/dom.ts @@ -192,9 +192,7 @@ export function set_data(text, data) { } export function set_input_value(input, value) { - if (value != null || input.value) { - input.value = value || ''; - } + input.value = value == null ? '' : value; } export function set_input_type(input, type) { From 40dca5252beaa669210a4d89512249937691131a Mon Sep 17 00:00:00 2001 From: Pat Cavit Date: Sun, 17 May 2020 08:49:37 -0700 Subject: [PATCH 66/69] WIP: Don't attempt to unsub if the iframe is destroyed (#4782) Fixes #4752 by not attempting to call .removeEventListener if the iframe.contentWindow no longer exists. --- src/runtime/internal/dom.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/runtime/internal/dom.ts b/src/runtime/internal/dom.ts index 1823cba273..02fd3dc23e 100644 --- a/src/runtime/internal/dom.ts +++ b/src/runtime/internal/dom.ts @@ -270,9 +270,11 @@ export function add_resize_listener(node: HTMLElement, fn: () => void) { iframe.setAttribute('aria-hidden', 'true'); iframe.tabIndex = -1; + const crossorigin = is_crossorigin(); + let unsubscribe: () => void; - if (is_crossorigin()) { + if (crossorigin) { iframe.src = `data:text/html,`; unsubscribe = listen(window, 'message', (event: MessageEvent) => { if (event.source === iframe.contentWindow) fn(); @@ -287,8 +289,13 @@ export function add_resize_listener(node: HTMLElement, fn: () => void) { append(node, iframe); return () => { + if (crossorigin) { + unsubscribe(); + } else if (unsubscribe && iframe.contentWindow) { + unsubscribe(); + } + detach(iframe); - if (unsubscribe) unsubscribe(); }; } From f7d1bf35a08a201dae161d54790e59a663830dc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Lins?= Date: Sun, 17 May 2020 12:50:38 -0300 Subject: [PATCH 67/69] A11y rule no-onchange (#4788) * A11y rule no-onchange * Fix message * Fix tests * Removing declarations --- src/compiler/compile/nodes/Element.ts | 21 +++++++++++- .../samples/a11y-no-onchange/input.svelte | 16 ++++++++++ .../samples/a11y-no-onchange/warnings.json | 32 +++++++++++++++++++ 3 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 test/validator/samples/a11y-no-onchange/input.svelte create mode 100644 test/validator/samples/a11y-no-onchange/warnings.json diff --git a/src/compiler/compile/nodes/Element.ts b/src/compiler/compile/nodes/Element.ts index 7adf46076d..b3503e533f 100644 --- a/src/compiler/compile/nodes/Element.ts +++ b/src/compiler/compile/nodes/Element.ts @@ -56,6 +56,11 @@ const a11y_required_content = new Set([ 'h6' ]); +const a11y_no_onchange = new Set([ + 'select', + 'option' +]); + const invisible_elements = new Set(['meta', 'html', 'script', 'style']); const valid_modifiers = new Set([ @@ -424,13 +429,18 @@ export default class Element extends Node { } validate_special_cases() { - const { component,attributes } = this; + const { component, attributes, handlers } = this; const attribute_map = new Map(); + const handlers_map = new Map(); attributes.forEach(attribute => ( attribute_map.set(attribute.name, attribute) )); + handlers.forEach(handler => ( + handlers_map.set(handler.name, handler) + )); + if (this.name === 'a') { const href_attribute = attribute_map.get('href') || attribute_map.get('xlink:href'); const id_attribute = attribute_map.get('id'); @@ -496,6 +506,15 @@ export default class Element extends Node { } } } + + if (a11y_no_onchange.has(this.name)) { + if (handlers_map.has('change') && !handlers_map.has('blur')) { + component.warn(this, { + code: `a11y-no-onchange`, + message: `A11y: on:blur must be used instead of on:change, unless absolutely necessary and it causes no negative consequences for keyboard only or screen reader users.` + }); + } + } } validate_bindings() { diff --git a/test/validator/samples/a11y-no-onchange/input.svelte b/test/validator/samples/a11y-no-onchange/input.svelte new file mode 100644 index 0000000000..af2041f406 --- /dev/null +++ b/test/validator/samples/a11y-no-onchange/input.svelte @@ -0,0 +1,16 @@ + + + + \ No newline at end of file diff --git a/test/validator/samples/a11y-no-onchange/warnings.json b/test/validator/samples/a11y-no-onchange/warnings.json new file mode 100644 index 0000000000..461f546c0b --- /dev/null +++ b/test/validator/samples/a11y-no-onchange/warnings.json @@ -0,0 +1,32 @@ +[ + { + "code": "a11y-no-onchange", + "end": { + "character": 88, + "column": 9, + "line": 4 + }, + "message": "A11y: on:blur must be used instead of on:change, unless absolutely necessary and it causes no negative consequences for keyboard only or screen reader users.", + "pos": 0, + "start": { + "character": 0, + "column": 0, + "line": 1 + } + }, + { + "code": "a11y-no-onchange", + "end": { + "character": 249, + "column": 44, + "line": 10 + }, + "message": "A11y: on:blur must be used instead of on:change, unless absolutely necessary and it causes no negative consequences for keyboard only or screen reader users.", + "pos": 209, + "start": { + "character": 209, + "column": 4, + "line": 10 + } + } +] From d5e95594e9676b9760bde20007c881b0a7f269ae Mon Sep 17 00:00:00 2001 From: pngwn Date: Sun, 17 May 2020 17:03:22 +0100 Subject: [PATCH 68/69] feat(site): add svelte syntax highlighting (#4851) --- site/content/docs/01-component-format.md | 24 ++-- site/content/docs/02-template-syntax.md | 142 +++++++++++------------ site/content/docs/03-run-time.md | 42 +++---- site/package-lock.json | 11 +- site/package.json | 3 +- site/src/utils/highlight.js | 1 + 6 files changed, 115 insertions(+), 108 deletions(-) diff --git a/site/content/docs/01-component-format.md b/site/content/docs/01-component-format.md index fa851ccc41..8eac5d3c50 100644 --- a/site/content/docs/01-component-format.md +++ b/site/content/docs/01-component-format.md @@ -8,7 +8,7 @@ Components are the building blocks of Svelte applications. They are written into All three sections — script, styles and markup — are optional. -```html +```sv @@ -30,7 +30,7 @@ A ` @@ -26,7 +26,7 @@ A lowercase tag, like `
    `, denotes a regular HTML element. A capitalised tag By default, attributes work exactly like their HTML counterparts. -```html +```sv
    @@ -36,7 +36,7 @@ By default, attributes work exactly like their HTML counterparts. As in HTML, values may be unquoted. -```html +```sv ``` @@ -44,7 +44,7 @@ As in HTML, values may be unquoted. Attribute values can contain JavaScript expressions. -```html +```sv page {p} ``` @@ -52,7 +52,7 @@ Attribute values can contain JavaScript expressions. Or they can *be* JavaScript expressions. -```html +```sv ``` @@ -71,7 +71,7 @@ All other attributes are included unless their value is [nullish](https://develo An expression might include characters that would cause syntax highlighting to fail in regular HTML, so quoting the value is permitted. The quotes do not affect how the value is parsed: -```html +```sv ``` @@ -79,7 +79,7 @@ An expression might include characters that would cause syntax highlighting to f When the attribute name and value match (`name={name}`), they can be replaced with `{name}`. -```html +```sv @@ -91,7 +91,7 @@ By convention, values passed to components are referred to as *properties* or *p As with elements, `name={name}` can be replaced with the `{name}` shorthand. -```html +```sv ``` @@ -101,7 +101,7 @@ As with elements, `name={name}` can be replaced with the `{name}` shorthand. An element or component can have multiple spread attributes, interspersed with regular ones. -```html +```sv ``` @@ -109,7 +109,7 @@ An element or component can have multiple spread attributes, interspersed with r *`$$props`* references all props that are passed to a component – including ones that are not declared with `export`. It is useful in rare cases, but not generally recommended, as it is difficult for Svelte to optimise. -```html +```sv ``` @@ -133,7 +133,7 @@ An element or component can have multiple spread attributes, interspersed with r Text can also contain JavaScript expressions: -```html +```sv

    Hello {name}!

    {a} + {b} = {a + b}.

    ``` @@ -145,7 +145,7 @@ Text can also contain JavaScript expressions: You can use HTML comments inside components. -```html +```sv

    Hello world

    ``` @@ -154,7 +154,7 @@ You can use HTML comments inside components. Comments beginning with `svelte-ignore` disable warnings for the next block of markup. Usually these are accessibility warnings; make sure that you're disabling them for a good reason. -```html +```sv ``` @@ -176,7 +176,7 @@ Comments beginning with `svelte-ignore` disable warnings for the next block of m Content that is conditionally rendered can be wrapped in an if block. -```html +```sv {#if answer === 42}

    what was the question?

    {/if} @@ -186,7 +186,7 @@ Content that is conditionally rendered can be wrapped in an if block. Additional conditions can be added with `{:else if expression}`, optionally ending in an `{:else}` clause. -```html +```sv {#if porridge.temperature > 100}

    too hot!

    {:else if 80 > porridge.temperature} @@ -219,7 +219,7 @@ Additional conditions can be added with `{:else if expression}`, optionally endi Iterating over lists of values can be done with an each block. -```html +```sv

    Shopping list

      {#each items as item} @@ -234,7 +234,7 @@ You can use each blocks to iterate over any array or array-like value — that i An each block can also specify an *index*, equivalent to the second argument in an `array.map(...)` callback: -```html +```sv {#each items as item, i}
    • {i + 1}: {item.name} x {item.qty}
    • {/each} @@ -244,7 +244,7 @@ An each block can also specify an *index*, equivalent to the second argument in If a *key* expression is provided — which must uniquely identify each list item — Svelte will use it to diff the list when data changes, rather than adding or removing items at the end. The key can be any object, but strings and numbers are recommended since they allow identity to persist when the objects themselves change. -```html +```sv {#each items as item (item.id)}
    • {item.name} x {item.qty}
    • {/each} @@ -259,7 +259,7 @@ If a *key* expression is provided — which must uniquely identify each list ite You can freely use destructuring and rest patterns in each blocks. -```html +```sv {#each items as { id, name, qty }, i (id)}
    • {i + 1}: {name} x {qty}
    • {/each} @@ -277,7 +277,7 @@ You can freely use destructuring and rest patterns in each blocks. An each block can also have an `{:else}` clause, which is rendered if the list is empty. -```html +```sv {#each todos as todo}

      {todo.text}

      {:else} @@ -302,7 +302,7 @@ An each block can also have an `{:else}` clause, which is rendered if the list i Await blocks allow you to branch on the three possible states of a Promise — pending, fulfilled or rejected. -```html +```sv {#await promise}

      waiting for the promise to resolve...

      @@ -319,7 +319,7 @@ Await blocks allow you to branch on the three possible states of a Promise — p The `catch` block can be omitted if you don't need to render anything when the promise rejects (or no error is possible). -```html +```sv {#await promise}

      waiting for the promise to resolve...

      @@ -333,7 +333,7 @@ The `catch` block can be omitted if you don't need to render anything when the p If you don't care about the pending state, you can also omit the initial block. -```html +```sv {#await promise then value}

      The value is {value}

      {/await} @@ -354,7 +354,7 @@ The expression should be valid standalone HTML — `{@html "
      "}content{@html > Svelte does not sanitize expressions before injecting HTML. If the data comes from an untrusted source, you must sanitize it, or you are exposing your users to an XSS vulnerability. -```html +```sv

      {post.title}

      {@html post.content} @@ -377,7 +377,7 @@ The `{@debug ...}` tag offers an alternative to `console.log(...)`. It logs the It accepts a comma-separated list of variable names (not arbitrary expressions). -```html +```sv @@ -1349,7 +1349,7 @@ The `` element renders a component dynamically, using the comp If `this` is falsy, no component is rendered. -```html +```sv ``` @@ -1367,7 +1367,7 @@ If `this` is falsy, no component is rendered. The `` element allows you to add event listeners to the `window` object without worrying about removing them when the component is destroyed, or checking for the existence of `window` when server-side rendering. -```html +```sv @@ -582,7 +582,7 @@ Animates a `blur` filter alongside an element's opacity. * `opacity` (`number`, default 0) - the opacity value to animate out to and in from * `amount` (`number`, default 5) - the size of the blur in pixels -```html +```sv @@ -621,7 +621,7 @@ Animates the x and y positions and the opacity of an element. `in` transitions a You can see the `fly` transition in action in the [transition tutorial](tutorial/adding-parameters-to-transitions). -```html +```sv