diff --git a/CHANGELOG.md b/CHANGELOG.md
index ad33840527..106d091cee 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,23 @@
## Unreleased
+* Add `EventSource` to known globals ([#5463](https://github.com/sveltejs/svelte/issues/5463))
+* Fix compiler exception with `~`/`+` combinators and `{...spread}` attributes ([#5465](https://github.com/sveltejs/svelte/issues/5465))
+
+## 3.28.0
+
+* Add `{#key}` block for keying arbitrary content on an expression ([#1469](https://github.com/sveltejs/svelte/issues/1469))
+
+## 3.27.0
+
+* Add `|nonpassive` event modifier, explicitly passing `passive: false` ([#2068](https://github.com/sveltejs/svelte/issues/2068))
+* Scope CSS selectors with `~` and `+` combinators ([#3104](https://github.com/sveltejs/svelte/issues/3104))
+* Fix keyed `{#each}` not reacting to key changing ([#5444](https://github.com/sveltejs/svelte/issues/5444))
+* Fix destructuring into store values ([#5449](https://github.com/sveltejs/svelte/issues/5449))
+* Fix erroneous `missing-declaration` warning with `use:obj.method` ([#5451](https://github.com/sveltejs/svelte/issues/5451))
+
+## 3.26.0
+
* Support `use:obj.method` as actions ([#3935](https://github.com/sveltejs/svelte/issues/3935))
* Support `_` as numeric separator ([#5407](https://github.com/sveltejs/svelte/issues/5407))
* Fix assignments to properties on store values ([#5412](https://github.com/sveltejs/svelte/issues/5412))
diff --git a/package-lock.json b/package-lock.json
index 2e2a95b362..4d6b1f8b6a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
{
"name": "svelte",
- "version": "3.25.1",
+ "version": "3.28.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
diff --git a/package.json b/package.json
index 7ebb41fdfb..7be5b19819 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "svelte",
- "version": "3.25.1",
+ "version": "3.28.0",
"description": "Cybernetically enhanced web apps",
"module": "index.mjs",
"main": "index",
diff --git a/site/content/blog/2019-04-15-setting-up-your-editor.md b/site/content/blog/2019-04-15-setting-up-your-editor.md
index b80223d820..fb8287d0cb 100644
--- a/site/content/blog/2019-04-15-setting-up-your-editor.md
+++ b/site/content/blog/2019-04-15-setting-up-your-editor.md
@@ -30,7 +30,9 @@ To treat `*.svelte` files as HTML, open *__Edit → Config...__* and add the fol
## Vim/Neovim
-To treat all `*.svelte` files as HTML, add the following line to your `init.vim`:
+You can use the [coc-svelte extension](https://github.com/coc-extensions/coc-svelte) which utilises the official language-server.
+
+As an alternative you can treat all `*.svelte` files as HTML. Add the following line to your `init.vim`:
```
au! BufNewFile,BufRead *.svelte set ft=html
@@ -50,13 +52,7 @@ To set the filetype for a single file, use a [modeline](https://vim.fandom.com/w
## Visual Studio Code
-To treat `*.svelte` files as HTML, add the following lines to your `settings.json` file:
-
-```cson
- "files.associations": {
- "*.svelte": "html"
- }
-```
+We recommend using the official [Svelte for VS Code extension](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode).
## JetBrains WebStorm
diff --git a/site/content/docs/02-template-syntax.md b/site/content/docs/02-template-syntax.md
index e93449728b..d955f650e2 100644
--- a/site/content/docs/02-template-syntax.md
+++ b/site/content/docs/02-template-syntax.md
@@ -342,6 +342,33 @@ If you don't care about the pending state, you can also omit the initial block.
{/await}
```
+### {#key ...}
+
+```sv
+{#key expression}...{/key}
+```
+
+Key blocks destroy and recreate their contents when the value of an expression changes.
+
+---
+
+This is useful if you want an element to play its transition whenever a value changes.
+
+```sv
+{#key value}
+
{value}
+{/key}
+```
+
+---
+
+When used around components, this will cause them to be reinstantiated and reinitialised.
+
+```sv
+{#key value}
+
+{/key}
+```
### {@html ...}
@@ -471,6 +498,7 @@ The following modifiers are available:
* `preventDefault` — calls `event.preventDefault()` before running the handler
* `stopPropagation` — calls `event.stopPropagation()`, preventing the event reaching the next element
* `passive` — improves scrolling performance on touch/wheel events (Svelte will add it automatically where it's safe to do so)
+* `nonpassive` — explicitly set `passive: false`
* `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
diff --git a/site/content/tutorial/01-introduction/07-making-an-app/text.md b/site/content/tutorial/01-introduction/07-making-an-app/text.md
index 4d044272b5..d75a9a626c 100644
--- a/site/content/tutorial/01-introduction/07-making-an-app/text.md
+++ b/site/content/tutorial/01-introduction/07-making-an-app/text.md
@@ -13,7 +13,7 @@ First, you'll need to integrate Svelte with a build tool. There are officially m
Don't worry if you're relatively new to web development and haven't used these tools before. We've prepared a simple step-by-step guide, [Svelte for new developers](blog/svelte-for-new-developers), which walks you through the process.
-You'll also want to configure your text editor to treat `.svelte` files the same as `.html` for the sake of syntax highlighting. [Read this guide to learn how](blog/setting-up-your-editor).
+You'll also want to configure your text editor. If you're using VS Code, install the [Svelte extension](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode), otherwise follow [this guide](blog/setting-up-your-editor) to configure your text editor to treat `.svelte` files the same as `.html` for the sake of syntax highlighting.
Then, once you've got your project set up, using Svelte components is easy. The compiler turns each component into a regular JavaScript class — just import it and instantiate with `new`:
diff --git a/site/content/tutorial/05-events/03-event-modifiers/text.md b/site/content/tutorial/05-events/03-event-modifiers/text.md
index 41154cafcd..2b2d6e6b31 100644
--- a/site/content/tutorial/05-events/03-event-modifiers/text.md
+++ b/site/content/tutorial/05-events/03-event-modifiers/text.md
@@ -21,6 +21,7 @@ The full list of modifiers:
* `preventDefault` — calls `event.preventDefault()` before running the handler. Useful for client-side form handling, for example.
* `stopPropagation` — calls `event.stopPropagation()`, preventing the event reaching the next element
* `passive` — improves scrolling performance on touch/wheel events (Svelte will add it automatically where it's safe to do so)
+* `nonpassive` — explicitly set `passive: false`
* `capture` — fires the handler during the *capture* phase instead of the *bubbling* phase ([MDN docs](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#Event_bubbling_and_capture))
* `once` — remove the handler after the first time it runs
* `self` — only trigger handler if event.target is the element itself
diff --git a/src/compiler/compile/Component.ts b/src/compiler/compile/Component.ts
index 078ecb8869..6c1fe89823 100644
--- a/src/compiler/compile/Component.ts
+++ b/src/compiler/compile/Component.ts
@@ -29,6 +29,7 @@ import add_to_set from './utils/add_to_set';
import check_graph_for_cycles from './utils/check_graph_for_cycles';
import { print, x, b } from 'code-red';
import { is_reserved_keyword } from './utils/reserved_keywords';
+import Element from './nodes/Element';
interface ComponentOptions {
namespace?: string;
@@ -85,6 +86,7 @@ export default class Component {
file: string;
locate: (c: number) => { line: number; column: number };
+ elements: Element[] = [];
stylesheet: Stylesheet;
aliases: Map = new Map();
@@ -171,8 +173,8 @@ export default class Component {
this.walk_instance_js_post_template();
+ this.elements.forEach(element => this.stylesheet.apply(element));
if (!compile_options.customElement) this.stylesheet.reify();
-
this.stylesheet.warn_on_unused_selectors(this);
}
@@ -221,6 +223,10 @@ export default class Component {
return this.aliases.get(name);
}
+ apply_stylesheet(element: Element) {
+ this.elements.push(element);
+ }
+
global(name: string) {
const alias = this.alias(name);
this.globals.set(name, alias);
diff --git a/src/compiler/compile/css/Selector.ts b/src/compiler/compile/css/Selector.ts
index c45cda57e9..46e9b4c4f0 100644
--- a/src/compiler/compile/css/Selector.ts
+++ b/src/compiler/compile/css/Selector.ts
@@ -4,12 +4,20 @@ import { gather_possible_values, UNKNOWN } from './gather_possible_values';
import { CssNode } from './interfaces';
import Component from '../Component';
import Element from '../nodes/Element';
+import { INode } from '../nodes/interfaces';
+import EachBlock from '../nodes/EachBlock';
+import IfBlock from '../nodes/IfBlock';
+import AwaitBlock from '../nodes/AwaitBlock';
enum BlockAppliesToNode {
NotPossible,
Possible,
UnknownSelectorType
}
+enum NodeExist {
+ Probably = 1,
+ Definitely = 2,
+}
const whitelist_attribute_selector = new Map([
['details', new Set(['open'])]
@@ -39,10 +47,10 @@ export default class Selector {
this.used = this.local_blocks.length === 0;
}
- apply(node: Element, stack: Element[]) {
+ apply(node: Element) {
const to_encapsulate: any[] = [];
- apply_selector(this.local_blocks.slice(), node, stack.slice(), to_encapsulate);
+ apply_selector(this.local_blocks.slice(), node, to_encapsulate);
if (to_encapsulate.length > 0) {
to_encapsulate.forEach(({ node, block }) => {
@@ -149,7 +157,7 @@ export default class Selector {
}
}
-function apply_selector(blocks: Block[], node: Element, stack: Element[], to_encapsulate: any[]): boolean {
+function apply_selector(blocks: Block[], node: Element, to_encapsulate: any[]): boolean {
const block = blocks.pop();
if (!block) return false;
@@ -162,7 +170,7 @@ function apply_selector(blocks: Block[], node: Element, stack: Element[], to_enc
return false;
case BlockAppliesToNode.UnknownSelectorType:
- // bail. TODO figure out what these could be
+ // bail. TODO figure out what these could be
to_encapsulate.push({ node, block });
return true;
}
@@ -174,9 +182,10 @@ function apply_selector(blocks: Block[], node: Element, stack: Element[], to_enc
continue;
}
- for (const stack_node of stack) {
- if (block_might_apply_to_node(ancestor_block, stack_node) !== BlockAppliesToNode.NotPossible) {
- to_encapsulate.push({ node: stack_node, block: ancestor_block });
+ let parent = node;
+ while (parent = get_element_parent(parent)) {
+ if (block_might_apply_to_node(ancestor_block, parent) !== BlockAppliesToNode.NotPossible) {
+ to_encapsulate.push({ node: parent, block: ancestor_block });
}
}
@@ -193,12 +202,22 @@ function apply_selector(blocks: Block[], node: Element, stack: Element[], to_enc
return false;
} else if (block.combinator.name === '>') {
- if (apply_selector(blocks, stack.pop(), stack, to_encapsulate)) {
+ if (apply_selector(blocks, get_element_parent(node), to_encapsulate)) {
to_encapsulate.push({ node, block });
return true;
}
return false;
+ } else if (block.combinator.name === '+' || block.combinator.name === '~') {
+ const siblings = get_possible_element_siblings(node, block.combinator.name === '+');
+ let has_match = false;
+ for (const possible_sibling of siblings.keys()) {
+ if (apply_selector(blocks.slice(), possible_sibling, to_encapsulate)) {
+ to_encapsulate.push({ node, block });
+ has_match = true;
+ }
+ }
+ return has_match;
}
// TODO other combinators
@@ -376,6 +395,158 @@ function unquote(value: CssNode) {
return str;
}
+function get_element_parent(node: Element): Element | null {
+ let parent: INode = node;
+ while ((parent = parent.parent) && parent.type !== 'Element');
+ return parent as Element | null;
+}
+
+function get_possible_element_siblings(node: INode, adjacent_only: boolean): Map {
+ const result: Map = new Map();
+ let prev: INode = node;
+ while (prev = prev.prev) {
+ if (prev.type === 'Element') {
+ if (!prev.attributes.find(attr => attr.type === 'Attribute' && attr.name.toLowerCase() === 'slot')) {
+ result.set(prev, NodeExist.Definitely);
+ }
+
+ if (adjacent_only) {
+ break;
+ }
+ } else if (prev.type === 'EachBlock' || prev.type === 'IfBlock' || prev.type === 'AwaitBlock') {
+ const possible_last_child = get_possible_last_child(prev, adjacent_only);
+
+ add_to_map(possible_last_child, result);
+ if (adjacent_only && has_definite_elements(possible_last_child)) {
+ return result;
+ }
+ }
+ }
+
+ if (!prev || !adjacent_only) {
+ let parent: INode = node;
+ let skip_each_for_last_child = node.type === 'ElseBlock';
+ while ((parent = parent.parent) && (parent.type === 'EachBlock' || parent.type === 'IfBlock' || parent.type === 'ElseBlock' || parent.type === 'AwaitBlock')) {
+ const possible_siblings = get_possible_element_siblings(parent, adjacent_only);
+ add_to_map(possible_siblings, result);
+
+ if (parent.type === 'EachBlock') {
+ // first child of each block can select the last child of each block as previous sibling
+ if (skip_each_for_last_child) {
+ skip_each_for_last_child = false;
+ } else {
+ add_to_map(get_possible_last_child(parent, adjacent_only), result);
+ }
+ } else if (parent.type === 'ElseBlock') {
+ skip_each_for_last_child = true;
+ parent = parent.parent;
+ }
+
+ if (adjacent_only && has_definite_elements(possible_siblings)) {
+ break;
+ }
+ }
+ }
+
+ return result;
+}
+
+function get_possible_last_child(block: EachBlock | IfBlock | AwaitBlock, adjacent_only: boolean): Map {
+ const result: Map = new Map();
+
+ if (block.type === 'EachBlock') {
+ const each_result: Map = loop_child(block.children, adjacent_only);
+ const else_result: Map = block.else ? loop_child(block.else.children, adjacent_only) : new Map();
+
+ const not_exhaustive = !has_definite_elements(else_result);
+
+ if (not_exhaustive) {
+ mark_as_probably(each_result);
+ mark_as_probably(else_result);
+ }
+ add_to_map(each_result, result);
+ add_to_map(else_result, result);
+ } else if (block.type === 'IfBlock') {
+ const if_result: Map = loop_child(block.children, adjacent_only);
+ const else_result: Map = block.else ? loop_child(block.else.children, adjacent_only) : new Map();
+
+ const not_exhaustive = !has_definite_elements(if_result) || !has_definite_elements(else_result);
+
+ if (not_exhaustive) {
+ mark_as_probably(if_result);
+ mark_as_probably(else_result);
+ }
+
+ add_to_map(if_result, result);
+ add_to_map(else_result, result);
+ } else if (block.type === 'AwaitBlock') {
+ const pending_result: Map = block.pending ? loop_child(block.pending.children, adjacent_only) : new Map();
+ const then_result: Map = block.then ? loop_child(block.then.children, adjacent_only) : new Map();
+ const catch_result: Map = block.catch ? loop_child(block.catch.children, adjacent_only) : new Map();
+
+ const not_exhaustive = !has_definite_elements(pending_result) || !has_definite_elements(then_result) || !has_definite_elements(catch_result);
+
+ if (not_exhaustive) {
+ mark_as_probably(pending_result);
+ mark_as_probably(then_result);
+ mark_as_probably(catch_result);
+ }
+
+ add_to_map(pending_result, result);
+ add_to_map(then_result, result);
+ add_to_map(catch_result, result);
+ }
+
+ return result;
+}
+
+function has_definite_elements(result: Map): boolean {
+ if (result.size === 0) return false;
+ for (const exist of result.values()) {
+ if (exist === NodeExist.Definitely) {
+ return true;
+ }
+ }
+ return false;
+}
+
+function add_to_map(from: Map, to: Map) {
+ from.forEach((exist, element) => {
+ to.set(element, higher_existance(exist, to.get(element)));
+ });
+}
+
+function higher_existance(exist1: NodeExist | null, exist2: NodeExist | null): NodeExist {
+ if (exist1 === undefined || exist2 === undefined) return exist1 || exist2;
+ return exist1 > exist2 ? exist1 : exist2;
+}
+
+function mark_as_probably(result: Map) {
+ for (const key of result.keys()) {
+ result.set(key, NodeExist.Probably);
+ }
+}
+
+function loop_child(children: INode[], adjacent_only: boolean) {
+ const result: Map = new Map();
+ for (let i = children.length - 1; i >= 0; i--) {
+ const child = children[i];
+ if (child.type === 'Element') {
+ result.set(child, NodeExist.Definitely);
+ if (adjacent_only) {
+ break;
+ }
+ } else if (child.type === 'EachBlock' || child.type === 'IfBlock' || child.type === 'AwaitBlock') {
+ const child_result = get_possible_last_child(child, adjacent_only);
+ add_to_map(child_result, result);
+ if (adjacent_only && has_definite_elements(child_result)) {
+ break;
+ }
+ }
+ }
+ return result;
+}
+
class Block {
global: boolean;
combinator: CssNode;
diff --git a/src/compiler/compile/css/Stylesheet.ts b/src/compiler/compile/css/Stylesheet.ts
index 27438947ff..dc464d7df8 100644
--- a/src/compiler/compile/css/Stylesheet.ts
+++ b/src/compiler/compile/css/Stylesheet.ts
@@ -2,7 +2,7 @@ import MagicString from 'magic-string';
import { walk } from 'estree-walker';
import Selector from './Selector';
import Element from '../nodes/Element';
-import { Ast, TemplateNode } from '../../interfaces';
+import { Ast } from '../../interfaces';
import Component from '../Component';
import { CssNode } from './interfaces';
import hash from "../utils/hash";
@@ -51,8 +51,8 @@ class Rule {
this.declarations = node.block.children.map((node: CssNode) => new Declaration(node));
}
- apply(node: Element, stack: Element[]) {
- this.selectors.forEach(selector => selector.apply(node, stack)); // TODO move the logic in here?
+ apply(node: Element) {
+ this.selectors.forEach(selector => selector.apply(node)); // TODO move the logic in here?
}
is_used(dev: boolean) {
@@ -162,10 +162,10 @@ class Atrule {
this.declarations = [];
}
- apply(node: Element, stack: Element[]) {
+ apply(node: Element) {
if (this.node.name === 'media' || this.node.name === 'supports') {
this.children.forEach(child => {
- child.apply(node, stack);
+ child.apply(node);
});
}
@@ -364,15 +364,9 @@ export default class Stylesheet {
apply(node: Element) {
if (!this.has_styles) return;
- const stack: Element[] = [];
- let parent: TemplateNode = node;
- while (parent = parent.parent) {
- if (parent.type === 'Element') stack.unshift(parent as Element);
- }
-
for (let i = 0; i < this.children.length; i += 1) {
const child = this.children[i];
- child.apply(node, stack);
+ child.apply(node);
}
}
diff --git a/src/compiler/compile/nodes/Action.ts b/src/compiler/compile/nodes/Action.ts
index 86aefa0ced..dc6f080b71 100644
--- a/src/compiler/compile/nodes/Action.ts
+++ b/src/compiler/compile/nodes/Action.ts
@@ -11,10 +11,11 @@ export default class Action extends Node {
constructor(component: Component, parent, scope, info) {
super(component, parent, scope, info);
- component.warn_if_undefined(info.name, info, scope);
+ const object = info.name.split('.')[0];
+ component.warn_if_undefined(object, info, scope);
this.name = info.name;
- component.add_reference(info.name.split('.')[0]);
+ component.add_reference(object);
this.expression = info.expression
? new Expression(component, this, scope, info.expression)
diff --git a/src/compiler/compile/nodes/Element.ts b/src/compiler/compile/nodes/Element.ts
index 6636c6b87b..7c9272e5d3 100644
--- a/src/compiler/compile/nodes/Element.ts
+++ b/src/compiler/compile/nodes/Element.ts
@@ -16,6 +16,7 @@ import list from '../../utils/list';
import Let from './Let';
import TemplateScope from './shared/TemplateScope';
import { INode } from './interfaces';
+import Component from '../Component';
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)$/;
@@ -80,6 +81,7 @@ const valid_modifiers = new Set([
'capture',
'once',
'passive',
+ 'nonpassive',
'self'
]);
@@ -123,7 +125,7 @@ export default class Element extends Node {
namespace: string;
needs_manual_style_scoping: boolean;
- constructor(component, parent, scope, info: any) {
+ constructor(component: Component, parent, scope, info: any) {
super(component, parent, scope, info);
this.name = info.name;
@@ -184,7 +186,7 @@ export default class Element extends Node {
case 'Attribute':
case 'Spread':
- // special case
+ // special case
if (node.name === 'xmlns') this.namespace = node.value[0].data;
this.attributes.push(new Attribute(component, this, scope, node));
@@ -235,7 +237,7 @@ export default class Element extends Node {
this.validate();
- component.stylesheet.apply(this);
+ component.apply_stylesheet(this);
}
validate() {
@@ -770,6 +772,13 @@ export default class Element extends Node {
});
}
+ if (handler.modifiers.has('passive') && handler.modifiers.has('nonpassive')) {
+ component.error(handler, {
+ code: 'invalid-event-modifier',
+ message: `The 'passive' and 'nonpassive' modifiers cannot be used together`
+ });
+ }
+
handler.modifiers.forEach(modifier => {
if (!valid_modifiers.has(modifier)) {
component.error(handler, {
@@ -804,7 +813,7 @@ export default class Element extends Node {
}
});
- if (passive_events.has(handler.name) && handler.can_make_passive && !handler.modifiers.has('preventDefault')) {
+ if (passive_events.has(handler.name) && handler.can_make_passive && !handler.modifiers.has('preventDefault') && !handler.modifiers.has('nonpassive')) {
// touch/wheel events should be passive by default
handler.modifiers.add('passive');
}
diff --git a/src/compiler/compile/nodes/KeyBlock.ts b/src/compiler/compile/nodes/KeyBlock.ts
new file mode 100644
index 0000000000..356210b6b0
--- /dev/null
+++ b/src/compiler/compile/nodes/KeyBlock.ts
@@ -0,0 +1,19 @@
+import Expression from "./shared/Expression";
+import map_children from "./shared/map_children";
+import AbstractBlock from "./shared/AbstractBlock";
+
+export default class KeyBlock extends AbstractBlock {
+ type: "KeyBlock";
+
+ expression: Expression;
+
+ constructor(component, parent, scope, info) {
+ super(component, parent, scope, info);
+
+ this.expression = new Expression(component, this, scope, info.expression);
+
+ this.children = map_children(component, this, scope, info.children);
+
+ this.warn_if_empty_block();
+ }
+}
diff --git a/src/compiler/compile/nodes/interfaces.ts b/src/compiler/compile/nodes/interfaces.ts
index 752168a49d..51d7b17e07 100644
--- a/src/compiler/compile/nodes/interfaces.ts
+++ b/src/compiler/compile/nodes/interfaces.ts
@@ -18,6 +18,7 @@ import Fragment from './Fragment';
import Head from './Head';
import IfBlock from './IfBlock';
import InlineComponent from './InlineComponent';
+import KeyBlock from './KeyBlock';
import Let from './Let';
import MustacheTag from './MustacheTag';
import Options from './Options';
@@ -50,6 +51,7 @@ export type INode = Action
| Head
| IfBlock
| InlineComponent
+| KeyBlock
| Let
| MustacheTag
| Options
diff --git a/src/compiler/compile/nodes/shared/map_children.ts b/src/compiler/compile/nodes/shared/map_children.ts
index dcdc52f86d..5d5da223fb 100644
--- a/src/compiler/compile/nodes/shared/map_children.ts
+++ b/src/compiler/compile/nodes/shared/map_children.ts
@@ -6,6 +6,7 @@ import Element from '../Element';
import Head from '../Head';
import IfBlock from '../IfBlock';
import InlineComponent from '../InlineComponent';
+import KeyBlock from '../KeyBlock';
import MustacheTag from '../MustacheTag';
import Options from '../Options';
import RawMustacheTag from '../RawMustacheTag';
@@ -28,6 +29,7 @@ function get_constructor(type) {
case 'Head': return Head;
case 'IfBlock': return IfBlock;
case 'InlineComponent': return InlineComponent;
+ case 'KeyBlock': return KeyBlock;
case 'MustacheTag': return MustacheTag;
case 'Options': return Options;
case 'RawMustacheTag': return RawMustacheTag;
diff --git a/src/compiler/compile/render_dom/invalidate.ts b/src/compiler/compile/render_dom/invalidate.ts
index b045db079f..b891b48cb5 100644
--- a/src/compiler/compile/render_dom/invalidate.ts
+++ b/src/compiler/compile/render_dom/invalidate.ts
@@ -36,47 +36,46 @@ export function invalidate(renderer: Renderer, scope: Scope, node: Node, names:
return renderer.invalidate(variable.name, undefined, main_execution_context);
}
- if (head) {
- component.has_reactive_assignments = true;
-
- if (node.type === 'AssignmentExpression' && node.operator === '=' && nodes_match(node.left, node.right) && tail.length === 0) {
- return get_invalidated(head, node);
- } else {
- const is_store_value = head.name[0] === '$' && head.name[1] !== '$';
- const extra_args = tail.map(variable => get_invalidated(variable)).filter(Boolean);
-
- const pass_value = (
- !main_execution_context &&
- (
- extra_args.length > 0 ||
- (node.type === 'AssignmentExpression' && node.left.type !== 'Identifier') ||
- (node.type === 'UpdateExpression' && (!node.prefix || node.argument.type !== 'Identifier'))
- )
- );
+ if (!head) {
+ return node;
+ }
- if (pass_value) {
- extra_args.unshift({
- type: 'Identifier',
- name: head.name
- });
- }
+ component.has_reactive_assignments = true;
- let invalidate = is_store_value
- ? x`@set_store_value(${head.name.slice(1)}, ${node}, ${head.name})`
- : !main_execution_context
- ? x`$$invalidate(${renderer.context_lookup.get(head.name).index}, ${node}, ${extra_args})`
- : extra_args.length
- ? [node, ...extra_args]
- : node;
+ if (node.type === 'AssignmentExpression' && node.operator === '=' && nodes_match(node.left, node.right) && tail.length === 0) {
+ return get_invalidated(head, node);
+ }
- if (head.subscribable && head.reassigned) {
- const subscribe = `$$subscribe_${head.name}`;
- invalidate = x`${subscribe}(${invalidate})`;
- }
+ const is_store_value = head.name[0] === '$' && head.name[1] !== '$';
+ const extra_args = tail.map(variable => get_invalidated(variable)).filter(Boolean);
- return invalidate;
+ if (is_store_value) {
+ return x`@set_store_value(${head.name.slice(1)}, ${node}, ${head.name}, ${extra_args})`;
+ }
+
+ let invalidate;
+ if (!main_execution_context) {
+ const pass_value = (
+ extra_args.length > 0 ||
+ (node.type === 'AssignmentExpression' && node.left.type !== 'Identifier') ||
+ (node.type === 'UpdateExpression' && (!node.prefix || node.argument.type !== 'Identifier'))
+ );
+ if (pass_value) {
+ extra_args.unshift({
+ type: 'Identifier',
+ name: head.name
+ });
}
+ invalidate = x`$$invalidate(${renderer.context_lookup.get(head.name).index}, ${node}, ${extra_args})`;
+ } else {
+ // skip `$$invalidate` if it is in the main execution context
+ invalidate = extra_args.length ? [node, ...extra_args] : node;
+ }
+
+ if (head.subscribable && head.reassigned) {
+ const subscribe = `$$subscribe_${head.name}`;
+ invalidate = x`${subscribe}(${invalidate})`;
}
- return node;
+ return invalidate;
}
\ No newline at end of file
diff --git a/src/compiler/compile/render_dom/wrappers/EachBlock.ts b/src/compiler/compile/render_dom/wrappers/EachBlock.ts
index 2513ea1adc..fe1c21a8df 100644
--- a/src/compiler/compile/render_dom/wrappers/EachBlock.ts
+++ b/src/compiler/compile/render_dom/wrappers/EachBlock.ts
@@ -239,6 +239,11 @@ export default class EachBlockWrapper extends Wrapper {
this.node.expression.dynamic_dependencies().forEach((dependency: string) => {
all_dependencies.add(dependency);
});
+ if (this.node.key) {
+ this.node.key.dynamic_dependencies().forEach((dependency: string) => {
+ all_dependencies.add(dependency);
+ });
+ }
this.dependencies = all_dependencies;
if (this.node.key) {
diff --git a/src/compiler/compile/render_dom/wrappers/Element/EventHandler.ts b/src/compiler/compile/render_dom/wrappers/Element/EventHandler.ts
index 157e186ea6..2fa2e9291a 100644
--- a/src/compiler/compile/render_dom/wrappers/Element/EventHandler.ts
+++ b/src/compiler/compile/render_dom/wrappers/Element/EventHandler.ts
@@ -45,11 +45,17 @@ export default class EventHandlerWrapper {
const args = [];
- const opts = ['passive', 'once', 'capture'].filter(mod => this.node.modifiers.has(mod));
+ const opts = ['nonpassive', 'passive', 'once', 'capture'].filter(mod => this.node.modifiers.has(mod));
if (opts.length) {
- args.push((opts.length === 1 && opts[0] === 'capture')
- ? TRUE
- : x`{ ${opts.map(opt => p`${opt}: true`)} }`);
+ if (opts.length === 1 && opts[0] === 'capture') {
+ args.push(TRUE);
+ } else {
+ args.push(x`{ ${ opts.map(opt =>
+ opt === 'nonpassive'
+ ? p`passive: false`
+ : p`${opt}: true`
+ ) } }`);
+ }
} else if (block.renderer.options.dev) {
args.push(FALSE);
}
diff --git a/src/compiler/compile/render_dom/wrappers/Fragment.ts b/src/compiler/compile/render_dom/wrappers/Fragment.ts
index a0984b69b9..853a41f3cc 100644
--- a/src/compiler/compile/render_dom/wrappers/Fragment.ts
+++ b/src/compiler/compile/render_dom/wrappers/Fragment.ts
@@ -6,6 +6,7 @@ import EachBlock from './EachBlock';
import Element from './Element/index';
import Head from './Head';
import IfBlock from './IfBlock';
+import KeyBlock from './KeyBlock';
import InlineComponent from './InlineComponent/index';
import MustacheTag from './MustacheTag';
import RawMustacheTag from './RawMustacheTag';
@@ -30,6 +31,7 @@ const wrappers = {
Head,
IfBlock,
InlineComponent,
+ KeyBlock,
MustacheTag,
Options: null,
RawMustacheTag,
diff --git a/src/compiler/compile/render_dom/wrappers/KeyBlock.ts b/src/compiler/compile/render_dom/wrappers/KeyBlock.ts
new file mode 100644
index 0000000000..359fb6946f
--- /dev/null
+++ b/src/compiler/compile/render_dom/wrappers/KeyBlock.ts
@@ -0,0 +1,136 @@
+import Wrapper from "./shared/Wrapper";
+import Renderer from "../Renderer";
+import Block from "../Block";
+import EachBlock from "../../nodes/EachBlock";
+import KeyBlock from "../../nodes/KeyBlock";
+import create_debugging_comment from "./shared/create_debugging_comment";
+import FragmentWrapper from "./Fragment";
+import { b, x } from "code-red";
+import { Identifier } from "estree";
+
+export default class KeyBlockWrapper extends Wrapper {
+ node: KeyBlock;
+ fragment: FragmentWrapper;
+ block: Block;
+ dependencies: string[];
+ var: Identifier = { type: "Identifier", name: "key_block" };
+
+ constructor(
+ renderer: Renderer,
+ block: Block,
+ parent: Wrapper,
+ node: EachBlock,
+ strip_whitespace: boolean,
+ next_sibling: Wrapper
+ ) {
+ super(renderer, block, parent, node);
+
+ this.cannot_use_innerhtml();
+ this.not_static_content();
+
+ this.dependencies = node.expression.dynamic_dependencies();
+
+ if (this.dependencies.length) {
+ block = block.child({
+ comment: create_debugging_comment(node, renderer.component),
+ name: renderer.component.get_unique_name("create_key_block"),
+ type: "key"
+ });
+ renderer.blocks.push(block);
+ }
+
+ this.block = block;
+ this.fragment = new FragmentWrapper(
+ renderer,
+ this.block,
+ node.children,
+ parent,
+ strip_whitespace,
+ next_sibling
+ );
+ }
+
+ render(block: Block, parent_node: Identifier, parent_nodes: Identifier) {
+ if (this.dependencies.length === 0) {
+ this.render_static_key(block, parent_node, parent_nodes);
+ } else {
+ this.render_dynamic_key(block, parent_node, parent_nodes);
+ }
+ }
+
+ render_static_key(_block: Block, parent_node: Identifier, parent_nodes: Identifier) {
+ this.fragment.render(this.block, parent_node, parent_nodes);
+ }
+
+ render_dynamic_key(block: Block, parent_node: Identifier, parent_nodes: Identifier) {
+ this.fragment.render(
+ this.block,
+ null,
+ (x`#nodes` as unknown) as Identifier
+ );
+
+ const has_transitions = !!(
+ this.block.has_intro_method || this.block.has_outro_method
+ );
+ const dynamic = this.block.has_update_method;
+
+ const previous_key = block.get_unique_name('previous_key');
+ const snippet = this.node.expression.manipulate(block);
+ block.add_variable(previous_key, snippet);
+
+ const not_equal = this.renderer.component.component_options.immutable ? x`@not_equal` : x`@safe_not_equal`;
+ const condition = x`${this.renderer.dirty(this.dependencies)} && ${not_equal}(${previous_key}, ${previous_key} = ${snippet})`;
+
+ block.chunks.init.push(b`
+ let ${this.var} = ${this.block.name}(#ctx);
+ `);
+ block.chunks.create.push(b`${this.var}.c();`);
+ if (this.renderer.options.hydratable) {
+ block.chunks.claim.push(b`${this.var}.l(${parent_nodes});`);
+ }
+ block.chunks.mount.push(
+ b`${this.var}.m(${parent_node || "#target"}, ${
+ parent_node ? "null" : "#anchor"
+ });`
+ );
+ const anchor = this.get_or_create_anchor(block, parent_node, parent_nodes);
+ const body = b`
+ ${
+ has_transitions
+ ? b`
+ @group_outros();
+ @transition_out(${this.var}, 1, 1, @noop);
+ @check_outros();
+ `
+ : b`${this.var}.d(1);`
+ }
+ ${this.var} = ${this.block.name}(#ctx);
+ ${this.var}.c();
+ ${has_transitions && b`@transition_in(${this.var})`}
+ ${this.var}.m(${this.get_update_mount_node(anchor)}, ${anchor});
+ `;
+
+ if (dynamic) {
+ block.chunks.update.push(b`
+ if (${condition}) {
+ ${body}
+ } else {
+ ${this.var}.p(#ctx, #dirty);
+ }
+ `);
+ } else {
+ block.chunks.update.push(b`
+ if (${condition}) {
+ ${body}
+ }
+ `);
+ }
+
+ if (has_transitions) {
+ block.chunks.intro.push(b`@transition_in(${this.var})`);
+ block.chunks.outro.push(b`@transition_out(${this.var})`);
+ }
+
+ block.chunks.destroy.push(b`${this.var}.d(detaching)`);
+ }
+}
diff --git a/src/compiler/compile/render_ssr/Renderer.ts b/src/compiler/compile/render_ssr/Renderer.ts
index fb9216327c..c633ff8b0a 100644
--- a/src/compiler/compile/render_ssr/Renderer.ts
+++ b/src/compiler/compile/render_ssr/Renderer.ts
@@ -7,6 +7,7 @@ import Head from './handlers/Head';
import HtmlTag from './handlers/HtmlTag';
import IfBlock from './handlers/IfBlock';
import InlineComponent from './handlers/InlineComponent';
+import KeyBlock from './handlers/KeyBlock';
import Slot from './handlers/Slot';
import Tag from './handlers/Tag';
import Text from './handlers/Text';
@@ -30,6 +31,7 @@ const handlers: Record = {
Head,
IfBlock,
InlineComponent,
+ KeyBlock,
MustacheTag: Tag, // TODO MustacheTag is an anachronism
Options: noop,
RawMustacheTag: HtmlTag,
diff --git a/src/compiler/compile/render_ssr/handlers/KeyBlock.ts b/src/compiler/compile/render_ssr/handlers/KeyBlock.ts
new file mode 100644
index 0000000000..33a6681280
--- /dev/null
+++ b/src/compiler/compile/render_ssr/handlers/KeyBlock.ts
@@ -0,0 +1,6 @@
+import KeyBlock from '../../nodes/KeyBlock';
+import Renderer, { RenderOptions } from '../Renderer';
+
+export default function(node: KeyBlock, renderer: Renderer, options: RenderOptions) {
+ renderer.render(node.children, options);
+}
diff --git a/src/compiler/parse/state/mustache.ts b/src/compiler/parse/state/mustache.ts
index dc26d994df..b72b77c30b 100644
--- a/src/compiler/parse/state/mustache.ts
+++ b/src/compiler/parse/state/mustache.ts
@@ -38,7 +38,7 @@ export default function mustache(parser: Parser) {
parser.allow_whitespace();
- // {/if}, {/each} or {/await}
+ // {/if}, {/each}, {/await} or {/key}
if (parser.eat('/')) {
let block = parser.current();
let expected;
@@ -63,6 +63,8 @@ export default function mustache(parser: Parser) {
expected = 'each';
} else if (block.type === 'AwaitBlock') {
expected = 'await';
+ } else if (block.type === 'KeyBlock') {
+ expected = 'key';
} else {
parser.error({
code: `unexpected-block-close`,
@@ -221,10 +223,12 @@ export default function mustache(parser: Parser) {
type = 'EachBlock';
} else if (parser.eat('await')) {
type = 'AwaitBlock';
+ } else if (parser.eat('key')) {
+ type = 'KeyBlock';
} else {
parser.error({
code: `expected-block-type`,
- message: `Expected if, each or await`
+ message: `Expected if, each, await or key`
});
}
diff --git a/src/compiler/utils/names.ts b/src/compiler/utils/names.ts
index 1fa37660f4..7b6a6f55e0 100644
--- a/src/compiler/utils/names.ts
+++ b/src/compiler/utils/names.ts
@@ -18,6 +18,7 @@ export const globals = new Set([
'Error',
'EvalError',
'Event',
+ 'EventSource',
'fetch',
'global',
'globalThis',
diff --git a/test/css/samples/general-siblings-combinator-await-not-exhaustive/_config.js b/test/css/samples/general-siblings-combinator-await-not-exhaustive/_config.js
new file mode 100644
index 0000000000..c81f1a9f82
--- /dev/null
+++ b/test/css/samples/general-siblings-combinator-await-not-exhaustive/_config.js
@@ -0,0 +1,3 @@
+export default {
+ warnings: []
+};
diff --git a/test/css/samples/general-siblings-combinator-await-not-exhaustive/expected.css b/test/css/samples/general-siblings-combinator-await-not-exhaustive/expected.css
new file mode 100644
index 0000000000..54eefc6088
--- /dev/null
+++ b/test/css/samples/general-siblings-combinator-await-not-exhaustive/expected.css
@@ -0,0 +1 @@
+.a.svelte-xyz~.b.svelte-xyz.svelte-xyz.svelte-xyz{color:green}.a.svelte-xyz~.c.svelte-xyz.svelte-xyz.svelte-xyz{color:green}.a.svelte-xyz~.d.svelte-xyz.svelte-xyz.svelte-xyz{color:green}.a.svelte-xyz~.e.svelte-xyz.svelte-xyz.svelte-xyz{color:green}.a.svelte-xyz~.f.svelte-xyz.svelte-xyz.svelte-xyz{color:green}.a.svelte-xyz~.g.svelte-xyz.svelte-xyz.svelte-xyz{color:green}.a.svelte-xyz~.h.svelte-xyz.svelte-xyz.svelte-xyz{color:green}.b.svelte-xyz~.d.svelte-xyz.svelte-xyz.svelte-xyz{color:green}.c.svelte-xyz~.d.svelte-xyz.svelte-xyz.svelte-xyz{color:green}.b.svelte-xyz~.e.svelte-xyz~.f.svelte-xyz~.h.svelte-xyz{color:green}.b.svelte-xyz~.d.svelte-xyz~.h.svelte-xyz.svelte-xyz{color:green}.c.svelte-xyz~.g.svelte-xyz.svelte-xyz.svelte-xyz{color:green}
\ No newline at end of file
diff --git a/test/css/samples/general-siblings-combinator-await-not-exhaustive/expected.html b/test/css/samples/general-siblings-combinator-await-not-exhaustive/expected.html
new file mode 100644
index 0000000000..de97b02a5e
--- /dev/null
+++ b/test/css/samples/general-siblings-combinator-await-not-exhaustive/expected.html
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/test/css/samples/general-siblings-combinator-await-not-exhaustive/input.svelte b/test/css/samples/general-siblings-combinator-await-not-exhaustive/input.svelte
new file mode 100644
index 0000000000..a677077c33
--- /dev/null
+++ b/test/css/samples/general-siblings-combinator-await-not-exhaustive/input.svelte
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+{#await promise then value}
+
+{:catch error}
+
+{/await}
+
+{#await promise}
+
+{:catch error}
+
+{/await}
+
+{#await promise}
+
+{:then error}
+
+{/await}
+
+
\ No newline at end of file
diff --git a/test/css/samples/general-siblings-combinator-await/_config.js b/test/css/samples/general-siblings-combinator-await/_config.js
new file mode 100644
index 0000000000..b4ebe41828
--- /dev/null
+++ b/test/css/samples/general-siblings-combinator-await/_config.js
@@ -0,0 +1,46 @@
+export default {
+ warnings: [
+ {
+ code: "css-unused-selector",
+ frame: `
+ 13:
+ 14: /* no match */
+ 15: .b ~ .c { color: green; }
+ ^
+ 16: .c ~ .d { color: green; }
+ 17: .b ~ .d { color: green; }`,
+ message: 'Unused CSS selector ".b ~ .c"',
+ pos: 269,
+ start: { character: 269, column: 1, line: 15 },
+ end: { character: 276, column: 8, line: 15 }
+ },
+ {
+ code: "css-unused-selector",
+ frame: `
+ 14: /* no match */
+ 15: .b ~ .c { color: green; }
+ 16: .c ~ .d { color: green; }
+ ^
+ 17: .b ~ .d { color: green; }
+ 18: `,
+ message: 'Unused CSS selector ".c ~ .d"',
+ pos: 296,
+ start: { character: 296, column: 1, line: 16 },
+ end: { character: 303, column: 8, line: 16 }
+ },
+ {
+ code: "css-unused-selector",
+ frame: `
+ 15: .b ~ .c { color: green; }
+ 16: .c ~ .d { color: green; }
+ 17: .b ~ .d { color: green; }
+ ^
+ 18:
+ 19:`,
+ message: 'Unused CSS selector ".b ~ .d"',
+ pos: 323,
+ start: { character: 323, column: 1, line: 17 },
+ end: { character: 330, column: 8, line: 17 }
+ }
+ ]
+};
diff --git a/test/css/samples/general-siblings-combinator-await/expected.css b/test/css/samples/general-siblings-combinator-await/expected.css
new file mode 100644
index 0000000000..94a5494597
--- /dev/null
+++ b/test/css/samples/general-siblings-combinator-await/expected.css
@@ -0,0 +1 @@
+.a.svelte-xyz~.b.svelte-xyz{color:green}.a.svelte-xyz~.c.svelte-xyz{color:green}.a.svelte-xyz~.d.svelte-xyz{color:green}.b.svelte-xyz~.e.svelte-xyz{color:green}.c.svelte-xyz~.e.svelte-xyz{color:green}.d.svelte-xyz~.e.svelte-xyz{color:green}.a.svelte-xyz~.e.svelte-xyz{color:green}
\ No newline at end of file
diff --git a/test/css/samples/general-siblings-combinator-await/expected.html b/test/css/samples/general-siblings-combinator-await/expected.html
new file mode 100644
index 0000000000..3d8ac9f966
--- /dev/null
+++ b/test/css/samples/general-siblings-combinator-await/expected.html
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/test/css/samples/general-siblings-combinator-await/input.svelte b/test/css/samples/general-siblings-combinator-await/input.svelte
new file mode 100644
index 0000000000..8aeadab170
--- /dev/null
+++ b/test/css/samples/general-siblings-combinator-await/input.svelte
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+{#await promise}
+
+{:then value}
+
+{:catch error}
+
+{/await}
+
+
\ No newline at end of file
diff --git a/test/css/samples/general-siblings-combinator-each-2/_config.js b/test/css/samples/general-siblings-combinator-each-2/_config.js
new file mode 100644
index 0000000000..c81f1a9f82
--- /dev/null
+++ b/test/css/samples/general-siblings-combinator-each-2/_config.js
@@ -0,0 +1,3 @@
+export default {
+ warnings: []
+};
diff --git a/test/css/samples/general-siblings-combinator-each-2/expected.css b/test/css/samples/general-siblings-combinator-each-2/expected.css
new file mode 100644
index 0000000000..d197058b24
--- /dev/null
+++ b/test/css/samples/general-siblings-combinator-each-2/expected.css
@@ -0,0 +1 @@
+.a.svelte-xyz~.b.svelte-xyz{color:green}.c.svelte-xyz~.d.svelte-xyz{color:green}.a.svelte-xyz~.d.svelte-xyz{color:green}.c.svelte-xyz~.b.svelte-xyz{color:green}.b.svelte-xyz~.c.svelte-xyz{color:green}.a.svelte-xyz~.c.svelte-xyz{color:green}
\ No newline at end of file
diff --git a/test/css/samples/general-siblings-combinator-each-2/expected.html b/test/css/samples/general-siblings-combinator-each-2/expected.html
new file mode 100644
index 0000000000..331a5e4317
--- /dev/null
+++ b/test/css/samples/general-siblings-combinator-each-2/expected.html
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/test/css/samples/general-siblings-combinator-each-2/input.svelte b/test/css/samples/general-siblings-combinator-each-2/input.svelte
new file mode 100644
index 0000000000..5bbdbdef66
--- /dev/null
+++ b/test/css/samples/general-siblings-combinator-each-2/input.svelte
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+{#each array as item}
+
+
+{/each}
+
+
\ No newline at end of file
diff --git a/test/css/samples/general-siblings-combinator-each-else-nested/_config.js b/test/css/samples/general-siblings-combinator-each-else-nested/_config.js
new file mode 100644
index 0000000000..cf241d856d
--- /dev/null
+++ b/test/css/samples/general-siblings-combinator-each-else-nested/_config.js
@@ -0,0 +1,18 @@
+export default {
+ warnings: [
+ {
+ code: "css-unused-selector",
+ frame: `
+ 33:
+ 34: /* no match */
+ 35: .e ~ .f { color: green; }
+ ^
+ 36:
+ 37:`,
+ message: 'Unused CSS selector ".e ~ .f"',
+ pos: 812,
+ start: { character: 812, column: 1, line: 35 },
+ end: { character: 819, column: 8, line: 35 }
+ }
+ ]
+};
diff --git a/test/css/samples/general-siblings-combinator-each-else-nested/expected.css b/test/css/samples/general-siblings-combinator-each-else-nested/expected.css
new file mode 100644
index 0000000000..b055f35ecd
--- /dev/null
+++ b/test/css/samples/general-siblings-combinator-each-else-nested/expected.css
@@ -0,0 +1 @@
+.a.svelte-xyz~.e.svelte-xyz.svelte-xyz.svelte-xyz{color:green}.a.svelte-xyz~.f.svelte-xyz.svelte-xyz.svelte-xyz{color:green}.b.svelte-xyz~.c.svelte-xyz.svelte-xyz.svelte-xyz{color:green}.b.svelte-xyz~.d.svelte-xyz.svelte-xyz.svelte-xyz{color:green}.c.svelte-xyz~.e.svelte-xyz.svelte-xyz.svelte-xyz{color:green}.c.svelte-xyz~.f.svelte-xyz.svelte-xyz.svelte-xyz{color:green}.d.svelte-xyz~.e.svelte-xyz.svelte-xyz.svelte-xyz{color:green}.d.svelte-xyz~.f.svelte-xyz.svelte-xyz.svelte-xyz{color:green}.e.svelte-xyz~.e.svelte-xyz.svelte-xyz.svelte-xyz{color:green}.i.svelte-xyz~.j.svelte-xyz.svelte-xyz.svelte-xyz{color:green}.g.svelte-xyz~.h.svelte-xyz~.j.svelte-xyz.svelte-xyz{color:green}.g.svelte-xyz~.i.svelte-xyz~.j.svelte-xyz.svelte-xyz{color:green}.m.svelte-xyz~.m.svelte-xyz.svelte-xyz.svelte-xyz{color:green}.m.svelte-xyz~.l.svelte-xyz.svelte-xyz.svelte-xyz{color:green}.l.svelte-xyz~.m.svelte-xyz.svelte-xyz.svelte-xyz{color:green}.a.svelte-xyz~.c.svelte-xyz.svelte-xyz.svelte-xyz{color:green}.a.svelte-xyz~.g.svelte-xyz.svelte-xyz.svelte-xyz{color:green}.b.svelte-xyz~.e.svelte-xyz.svelte-xyz.svelte-xyz{color:green}.c.svelte-xyz~.g.svelte-xyz.svelte-xyz.svelte-xyz{color:green}.c.svelte-xyz~.k.svelte-xyz.svelte-xyz.svelte-xyz{color:green}.d.svelte-xyz~.d.svelte-xyz.svelte-xyz.svelte-xyz{color:green}.g.svelte-xyz~.g.svelte-xyz.svelte-xyz.svelte-xyz{color:green}.h.svelte-xyz~.h.svelte-xyz.svelte-xyz.svelte-xyz{color:green}.i.svelte-xyz~.i.svelte-xyz.svelte-xyz.svelte-xyz{color:green}.j.svelte-xyz~.j.svelte-xyz.svelte-xyz.svelte-xyz{color:green}.g.svelte-xyz~.j.svelte-xyz.svelte-xyz.svelte-xyz{color:green}.g.svelte-xyz~.h.svelte-xyz~.i.svelte-xyz~.j.svelte-xyz{color:green}
\ No newline at end of file
diff --git a/test/css/samples/general-siblings-combinator-each-else-nested/expected.html b/test/css/samples/general-siblings-combinator-each-else-nested/expected.html
new file mode 100644
index 0000000000..67dd05f677
--- /dev/null
+++ b/test/css/samples/general-siblings-combinator-each-else-nested/expected.html
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/test/css/samples/general-siblings-combinator-each-else-nested/input.svelte b/test/css/samples/general-siblings-combinator-each-else-nested/input.svelte
new file mode 100644
index 0000000000..63a1123708
--- /dev/null
+++ b/test/css/samples/general-siblings-combinator-each-else-nested/input.svelte
@@ -0,0 +1,77 @@
+
+
+
+
+
+
+{#each array as a}
+
+ {#each array as b}
+
+ {:else}
+
+ {/each}
+{/each}
+
+{#each array as c}
+ {#each array as d}
+
+ {/each}
+{:else}
+
+{/each}
+
+{#each array as x}
+
+ {#each array as y}
+ {#each array as z}
+
+ {/each}
+ {:else}
+
+ {/each}
+
+{/each}
+
+
+
+{#each array as item}
+ {#each array as item}
+
+ {:else}
+
+ {/each}
+{/each}
\ No newline at end of file
diff --git a/test/css/samples/general-siblings-combinator-each-else/_config.js b/test/css/samples/general-siblings-combinator-each-else/_config.js
new file mode 100644
index 0000000000..4d9beceeb2
--- /dev/null
+++ b/test/css/samples/general-siblings-combinator-each-else/_config.js
@@ -0,0 +1,18 @@
+export default {
+ warnings: [
+ {
+ code: "css-unused-selector",
+ frame: `
+ 11:
+ 12: /* no match */
+ 13: .b ~ .c { color: green; }
+ ^
+ 14:
+ 15:`,
+ message: 'Unused CSS selector ".b ~ .c"',
+ pos: 199,
+ start: { character: 199, column: 1, line: 13 },
+ end: { character: 206, column: 8, line: 13 }
+ }
+ ]
+};
diff --git a/test/css/samples/general-siblings-combinator-each-else/expected.css b/test/css/samples/general-siblings-combinator-each-else/expected.css
new file mode 100644
index 0000000000..31fafa3243
--- /dev/null
+++ b/test/css/samples/general-siblings-combinator-each-else/expected.css
@@ -0,0 +1 @@
+.a.svelte-xyz~.b.svelte-xyz{color:green}.a.svelte-xyz~.c.svelte-xyz{color:green}.b.svelte-xyz~.d.svelte-xyz{color:green}.c.svelte-xyz~.d.svelte-xyz{color:green}.a.svelte-xyz~.d.svelte-xyz{color:green}
\ No newline at end of file
diff --git a/test/css/samples/general-siblings-combinator-each-else/expected.html b/test/css/samples/general-siblings-combinator-each-else/expected.html
new file mode 100644
index 0000000000..fb838a55fd
--- /dev/null
+++ b/test/css/samples/general-siblings-combinator-each-else/expected.html
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/test/css/samples/general-siblings-combinator-each-else/input.svelte b/test/css/samples/general-siblings-combinator-each-else/input.svelte
new file mode 100644
index 0000000000..5cd78853ca
--- /dev/null
+++ b/test/css/samples/general-siblings-combinator-each-else/input.svelte
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+{#each array as item}
+
+{:else}
+
+{/each}
+
+
\ No newline at end of file
diff --git a/test/css/samples/general-siblings-combinator-each-nested/_config.js b/test/css/samples/general-siblings-combinator-each-nested/_config.js
new file mode 100644
index 0000000000..c81f1a9f82
--- /dev/null
+++ b/test/css/samples/general-siblings-combinator-each-nested/_config.js
@@ -0,0 +1,3 @@
+export default {
+ warnings: []
+};
diff --git a/test/css/samples/general-siblings-combinator-each-nested/expected.css b/test/css/samples/general-siblings-combinator-each-nested/expected.css
new file mode 100644
index 0000000000..5bf1f832ae
--- /dev/null
+++ b/test/css/samples/general-siblings-combinator-each-nested/expected.css
@@ -0,0 +1 @@
+.a.svelte-xyz~.d.svelte-xyz.svelte-xyz{color:green}.a.svelte-xyz~.e.svelte-xyz.svelte-xyz{color:green}.a.svelte-xyz~.f.svelte-xyz.svelte-xyz{color:green}.a.svelte-xyz~.g.svelte-xyz.svelte-xyz{color:green}.c.svelte-xyz~.d.svelte-xyz.svelte-xyz{color:green}.c.svelte-xyz~.e.svelte-xyz.svelte-xyz{color:green}.c.svelte-xyz~.f.svelte-xyz.svelte-xyz{color:green}.c.svelte-xyz~.g.svelte-xyz.svelte-xyz{color:green}.j.svelte-xyz~.m.svelte-xyz.svelte-xyz{color:green}.j.svelte-xyz~.n.svelte-xyz.svelte-xyz{color:green}.j.svelte-xyz~.o.svelte-xyz.svelte-xyz{color:green}.k.svelte-xyz~.m.svelte-xyz.svelte-xyz{color:green}.k.svelte-xyz~.n.svelte-xyz.svelte-xyz{color:green}.k.svelte-xyz~.o.svelte-xyz.svelte-xyz{color:green}.l.svelte-xyz~.m.svelte-xyz.svelte-xyz{color:green}.l.svelte-xyz~.n.svelte-xyz.svelte-xyz{color:green}.l.svelte-xyz~.o.svelte-xyz.svelte-xyz{color:green}.d.svelte-xyz~.e.svelte-xyz.svelte-xyz{color:green}.e.svelte-xyz~.f.svelte-xyz.svelte-xyz{color:green}.g.svelte-xyz~.h.svelte-xyz.svelte-xyz{color:green}.f.svelte-xyz~.d.svelte-xyz.svelte-xyz{color:green}.f.svelte-xyz~.e.svelte-xyz.svelte-xyz{color:green}.f.svelte-xyz~.f.svelte-xyz.svelte-xyz{color:green}.h.svelte-xyz~.g.svelte-xyz.svelte-xyz{color:green}.i.svelte-xyz~.h.svelte-xyz.svelte-xyz{color:green}.i.svelte-xyz~.g.svelte-xyz.svelte-xyz{color:green}.d.svelte-xyz~.d.svelte-xyz.svelte-xyz{color:green}.e.svelte-xyz~.e.svelte-xyz.svelte-xyz{color:green}.f.svelte-xyz~.f.svelte-xyz.svelte-xyz{color:green}.g.svelte-xyz~.g.svelte-xyz.svelte-xyz{color:green}.h.svelte-xyz~.h.svelte-xyz.svelte-xyz{color:green}.i.svelte-xyz~.i.svelte-xyz.svelte-xyz{color:green}.e.svelte-xyz~.e.svelte-xyz~.f.svelte-xyz{color:green}.e.svelte-xyz~.e.svelte-xyz~.d.svelte-xyz{color:green}.h.svelte-xyz~.h.svelte-xyz~.i.svelte-xyz{color:green}.h.svelte-xyz~.h.svelte-xyz~.g.svelte-xyz{color:green}.a.svelte-xyz~.h.svelte-xyz.svelte-xyz{color:green}.a.svelte-xyz~.i.svelte-xyz.svelte-xyz{color:green}.c.svelte-xyz~.h.svelte-xyz.svelte-xyz{color:green}.c.svelte-xyz~.i.svelte-xyz.svelte-xyz{color:green}.d.svelte-xyz~.f.svelte-xyz.svelte-xyz{color:green}.d.svelte-xyz~.g.svelte-xyz.svelte-xyz{color:green}.e.svelte-xyz~.g.svelte-xyz.svelte-xyz{color:green}.g.svelte-xyz~.i.svelte-xyz.svelte-xyz{color:green}
\ No newline at end of file
diff --git a/test/css/samples/general-siblings-combinator-each-nested/expected.html b/test/css/samples/general-siblings-combinator-each-nested/expected.html
new file mode 100644
index 0000000000..340d6fc4c8
--- /dev/null
+++ b/test/css/samples/general-siblings-combinator-each-nested/expected.html
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test/css/samples/general-siblings-combinator-each-nested/input.svelte b/test/css/samples/general-siblings-combinator-each-nested/input.svelte
new file mode 100644
index 0000000000..b7c7377015
--- /dev/null
+++ b/test/css/samples/general-siblings-combinator-each-nested/input.svelte
@@ -0,0 +1,113 @@
+
+
+
+
+
+
+{#each array as item}
+
+
+{/each}
+
+{#each array as item}
+ {#each array as item}
+ {#each array as item}
+
+ {/each}
+
+ {/each}
+
+{/each}
+
+{#each array as item}
+
+ {#each array as item}
+
+ {#each array as item}
+
+ {/each}
+ {/each}
+{/each}
+
+{#each array as item}
+
+ {#each array as item}
+
+ {#each array as item}
+
+ {/each}
+ {/each}
+{/each}
+
+{#each array as item}
+ {#each array as item}
+ {#each array as item}
+
+ {/each}
+
+ {/each}
+
+{/each}
\ No newline at end of file
diff --git a/test/css/samples/general-siblings-combinator-each/expected.css b/test/css/samples/general-siblings-combinator-each/expected.css
new file mode 100644
index 0000000000..8c48251bdd
--- /dev/null
+++ b/test/css/samples/general-siblings-combinator-each/expected.css
@@ -0,0 +1 @@
+div.svelte-xyz~span.svelte-xyz{color:green}
\ No newline at end of file
diff --git a/test/css/samples/general-siblings-combinator-each/expected.html b/test/css/samples/general-siblings-combinator-each/expected.html
new file mode 100644
index 0000000000..9d0416f01b
--- /dev/null
+++ b/test/css/samples/general-siblings-combinator-each/expected.html
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/test/css/samples/general-siblings-combinator-each/input.svelte b/test/css/samples/general-siblings-combinator-each/input.svelte
new file mode 100644
index 0000000000..ce65da109d
--- /dev/null
+++ b/test/css/samples/general-siblings-combinator-each/input.svelte
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+{#each array as item}
+
+
+
+
+{/each}
+
+
\ No newline at end of file
diff --git a/test/css/samples/general-siblings-combinator-if-not-exhaustive-with-each/_config.js b/test/css/samples/general-siblings-combinator-if-not-exhaustive-with-each/_config.js
new file mode 100644
index 0000000000..0b5d391f50
--- /dev/null
+++ b/test/css/samples/general-siblings-combinator-if-not-exhaustive-with-each/_config.js
@@ -0,0 +1,18 @@
+export default {
+ warnings: [
+ {
+ code: "css-unused-selector",
+ frame: `
+ 16:
+ 17: /* no match */
+ 18: .b ~ .c { color: green; }
+ ^
+ 19:
+ 20:`,
+ message: 'Unused CSS selector ".b ~ .c"',
+ pos: 319,
+ start: { character: 319, column: 1, line: 18 },
+ end: { character: 326, column: 8, line: 18 }
+ }
+ ]
+};
diff --git a/test/css/samples/general-siblings-combinator-if-not-exhaustive-with-each/expected.css b/test/css/samples/general-siblings-combinator-if-not-exhaustive-with-each/expected.css
new file mode 100644
index 0000000000..e6a974efca
--- /dev/null
+++ b/test/css/samples/general-siblings-combinator-if-not-exhaustive-with-each/expected.css
@@ -0,0 +1 @@
+.a.svelte-xyz~.b.svelte-xyz.svelte-xyz.svelte-xyz{color:green}.a.svelte-xyz~.c.svelte-xyz.svelte-xyz.svelte-xyz{color:green}.a.svelte-xyz~.d.svelte-xyz.svelte-xyz.svelte-xyz{color:green}.b.svelte-xyz~.d.svelte-xyz.svelte-xyz.svelte-xyz{color:green}.c.svelte-xyz~.d.svelte-xyz.svelte-xyz.svelte-xyz{color:green}.a.svelte-xyz~.c.svelte-xyz~.c.svelte-xyz.svelte-xyz{color:green}.c.svelte-xyz~.c.svelte-xyz~.d.svelte-xyz.svelte-xyz{color:green}.a.svelte-xyz~.c.svelte-xyz~.c.svelte-xyz~.d.svelte-xyz{color:green}
\ No newline at end of file
diff --git a/test/css/samples/general-siblings-combinator-if-not-exhaustive-with-each/expected.html b/test/css/samples/general-siblings-combinator-if-not-exhaustive-with-each/expected.html
new file mode 100644
index 0000000000..fb838a55fd
--- /dev/null
+++ b/test/css/samples/general-siblings-combinator-if-not-exhaustive-with-each/expected.html
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/test/css/samples/general-siblings-combinator-if-not-exhaustive-with-each/input.svelte b/test/css/samples/general-siblings-combinator-if-not-exhaustive-with-each/input.svelte
new file mode 100644
index 0000000000..2b53f7b5b8
--- /dev/null
+++ b/test/css/samples/general-siblings-combinator-if-not-exhaustive-with-each/input.svelte
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+{#if foo}
+
+{:else}
+ {#each array as item}
+
+ {/each}
+{/if}
+
+
\ No newline at end of file
diff --git a/test/css/samples/general-siblings-combinator-if-not-exhaustive/_config.js b/test/css/samples/general-siblings-combinator-if-not-exhaustive/_config.js
new file mode 100644
index 0000000000..81fb595d68
--- /dev/null
+++ b/test/css/samples/general-siblings-combinator-if-not-exhaustive/_config.js
@@ -0,0 +1,18 @@
+export default {
+ warnings: [
+ {
+ code: "css-unused-selector",
+ frame: `
+ 12:
+ 13: /* no match */
+ 14: .b ~ .c { color: green; }
+ ^
+ 15:
+ 16:`,
+ message: 'Unused CSS selector ".b ~ .c"',
+ pos: 215,
+ start: { character: 215, column: 1, line: 14 },
+ end: { character: 222, column: 8, line: 14 }
+ }
+ ]
+};
diff --git a/test/css/samples/general-siblings-combinator-if-not-exhaustive/expected.css b/test/css/samples/general-siblings-combinator-if-not-exhaustive/expected.css
new file mode 100644
index 0000000000..dc7ee1b62b
--- /dev/null
+++ b/test/css/samples/general-siblings-combinator-if-not-exhaustive/expected.css
@@ -0,0 +1 @@
+.a.svelte-xyz~.b.svelte-xyz{color:green}.a.svelte-xyz~.c.svelte-xyz{color:green}.a.svelte-xyz~.d.svelte-xyz{color:green}.b.svelte-xyz~.d.svelte-xyz{color:green}.c.svelte-xyz~.d.svelte-xyz{color:green}
\ No newline at end of file
diff --git a/test/css/samples/general-siblings-combinator-if-not-exhaustive/expected.html b/test/css/samples/general-siblings-combinator-if-not-exhaustive/expected.html
new file mode 100644
index 0000000000..813e778dc6
--- /dev/null
+++ b/test/css/samples/general-siblings-combinator-if-not-exhaustive/expected.html
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/test/css/samples/general-siblings-combinator-if-not-exhaustive/input.svelte b/test/css/samples/general-siblings-combinator-if-not-exhaustive/input.svelte
new file mode 100644
index 0000000000..3e5c5af7a2
--- /dev/null
+++ b/test/css/samples/general-siblings-combinator-if-not-exhaustive/input.svelte
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+{#if foo}
+
+{:else if bar}
+
+{/if}
+
+
\ No newline at end of file
diff --git a/test/css/samples/general-siblings-combinator-if/_config.js b/test/css/samples/general-siblings-combinator-if/_config.js
new file mode 100644
index 0000000000..cbc3d8a784
--- /dev/null
+++ b/test/css/samples/general-siblings-combinator-if/_config.js
@@ -0,0 +1,46 @@
+export default {
+ warnings: [
+ {
+ code: "css-unused-selector",
+ frame: `
+ 14:
+ 15: /* no match */
+ 16: .b ~ .c { color: green; }
+ ^
+ 17: .b ~ .d { color: green; }
+ 18: .c ~ .d { color: green; }`,
+ message: 'Unused CSS selector ".b ~ .c"',
+ pos: 269,
+ start: { character: 269, column: 1, line: 16 },
+ end: { character: 276, column: 8, line: 16 }
+ },
+ {
+ code: "css-unused-selector",
+ frame: `
+ 15: /* no match */
+ 16: .b ~ .c { color: green; }
+ 17: .b ~ .d { color: green; }
+ ^
+ 18: .c ~ .d { color: green; }
+ 19: `,
+ message: 'Unused CSS selector ".b ~ .d"',
+ pos: 296,
+ start: { character: 296, column: 1, line: 17 },
+ end: { character: 303, column: 8, line: 17 }
+ },
+ {
+ code: "css-unused-selector",
+ frame: `
+ 16: .b ~ .c { color: green; }
+ 17: .b ~ .d { color: green; }
+ 18: .c ~ .d { color: green; }
+ ^
+ 19:
+ 20:`,
+ message: 'Unused CSS selector ".c ~ .d"',
+ pos: 323,
+ start: { character: 323, column: 1, line: 18 },
+ end: { character: 330, column: 8, line: 18 }
+ }
+ ]
+};
diff --git a/test/css/samples/general-siblings-combinator-if/expected.css b/test/css/samples/general-siblings-combinator-if/expected.css
new file mode 100644
index 0000000000..94a5494597
--- /dev/null
+++ b/test/css/samples/general-siblings-combinator-if/expected.css
@@ -0,0 +1 @@
+.a.svelte-xyz~.b.svelte-xyz{color:green}.a.svelte-xyz~.c.svelte-xyz{color:green}.a.svelte-xyz~.d.svelte-xyz{color:green}.b.svelte-xyz~.e.svelte-xyz{color:green}.c.svelte-xyz~.e.svelte-xyz{color:green}.d.svelte-xyz~.e.svelte-xyz{color:green}.a.svelte-xyz~.e.svelte-xyz{color:green}
\ No newline at end of file
diff --git a/test/css/samples/general-siblings-combinator-if/expected.html b/test/css/samples/general-siblings-combinator-if/expected.html
new file mode 100644
index 0000000000..3d8ac9f966
--- /dev/null
+++ b/test/css/samples/general-siblings-combinator-if/expected.html
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/test/css/samples/general-siblings-combinator-if/input.svelte b/test/css/samples/general-siblings-combinator-if/input.svelte
new file mode 100644
index 0000000000..fca5499f2e
--- /dev/null
+++ b/test/css/samples/general-siblings-combinator-if/input.svelte
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+{#if foo}
+
+{:else if bar}
+
+{:else}
+
+{/if}
+
+
\ No newline at end of file
diff --git a/test/css/samples/general-siblings-combinator-slot/_config.js b/test/css/samples/general-siblings-combinator-slot/_config.js
new file mode 100644
index 0000000000..4cf743c0a0
--- /dev/null
+++ b/test/css/samples/general-siblings-combinator-slot/_config.js
@@ -0,0 +1,88 @@
+export default {
+ warnings: [
+ {
+ code: "css-unused-selector",
+ frame: `
+ 8:
+ 9: /* no match */
+ 10: .a ~ .b { color: green; }
+ ^
+ 11: .b ~ .c { color: green; }
+ 12: .c ~ .f { color: green; }`,
+ message: 'Unused CSS selector ".a ~ .b"',
+ pos: 111,
+ start: { character: 111, column: 1, line: 10 },
+ end: { character: 118, column: 8, line: 10 }
+ },
+ {
+ code: "css-unused-selector",
+ frame: `
+ 9: /* no match */
+ 10: .a ~ .b { color: green; }
+ 11: .b ~ .c { color: green; }
+ ^
+ 12: .c ~ .f { color: green; }
+ 13: .f ~ .g { color: green; }`,
+ message: 'Unused CSS selector ".b ~ .c"',
+ pos: 138,
+ start: { character: 138, column: 1, line: 11 },
+ end: { character: 145, column: 8, line: 11 }
+ },
+ {
+ code: "css-unused-selector",
+ frame: `
+ 10: .a ~ .b { color: green; }
+ 11: .b ~ .c { color: green; }
+ 12: .c ~ .f { color: green; }
+ ^
+ 13: .f ~ .g { color: green; }
+ 14: .b ~ .f { color: green; }`,
+ message: 'Unused CSS selector ".c ~ .f"',
+ pos: 165,
+ start: { character: 165, column: 1, line: 12 },
+ end: { character: 172, column: 8, line: 12 }
+ },
+ {
+ code: "css-unused-selector",
+ frame: `
+ 11: .b ~ .c { color: green; }
+ 12: .c ~ .f { color: green; }
+ 13: .f ~ .g { color: green; }
+ ^
+ 14: .b ~ .f { color: green; }
+ 15: .b ~ .g { color: green; }`,
+ message: 'Unused CSS selector ".f ~ .g"',
+ pos: 192,
+ start: { character: 192, column: 1, line: 13 },
+ end: { character: 199, column: 8, line: 13 }
+ },
+ {
+ code: "css-unused-selector",
+ frame: `
+ 12: .c ~ .f { color: green; }
+ 13: .f ~ .g { color: green; }
+ 14: .b ~ .f { color: green; }
+ ^
+ 15: .b ~ .g { color: green; }
+ 16: `,
+ message: 'Unused CSS selector ".b ~ .f"',
+ pos: 219,
+ start: { character: 219, column: 1, line: 14 },
+ end: { character: 226, column: 8, line: 14 }
+ },
+ {
+ code: "css-unused-selector",
+ frame: `
+ 13: .f ~ .g { color: green; }
+ 14: .b ~ .f { color: green; }
+ 15: .b ~ .g { color: green; }
+ ^
+ 16:
+ 17:`,
+ message: 'Unused CSS selector ".b ~ .g"',
+ pos: 246,
+ start: { character: 246, column: 1, line: 15 },
+ end: { character: 253, column: 8, line: 15 }
+ }
+ ]
+};
diff --git a/test/css/samples/general-siblings-combinator-slot/expected.css b/test/css/samples/general-siblings-combinator-slot/expected.css
new file mode 100644
index 0000000000..29325e0cef
--- /dev/null
+++ b/test/css/samples/general-siblings-combinator-slot/expected.css
@@ -0,0 +1 @@
+.d.svelte-xyz~.e.svelte-xyz{color:green}.a.svelte-xyz~.g.svelte-xyz{color:green}
\ No newline at end of file
diff --git a/test/css/samples/general-siblings-combinator-slot/input.svelte b/test/css/samples/general-siblings-combinator-slot/input.svelte
new file mode 100644
index 0000000000..e5af4c0962
--- /dev/null
+++ b/test/css/samples/general-siblings-combinator-slot/input.svelte
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test/css/samples/general-siblings-combinator-star/input.svelte b/test/css/samples/general-siblings-combinator-star/input.svelte
new file mode 100644
index 0000000000..a069685d4f
--- /dev/null
+++ b/test/css/samples/general-siblings-combinator-star/input.svelte
@@ -0,0 +1,17 @@
+
+
+
\ No newline at end of file
diff --git a/test/css/samples/siblings-combinator-star/input.svelte b/test/css/samples/siblings-combinator-star/input.svelte
new file mode 100644
index 0000000000..ca837f2239
--- /dev/null
+++ b/test/css/samples/siblings-combinator-star/input.svelte
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test/css/samples/siblings-combinator-with-spread/_config.js b/test/css/samples/siblings-combinator-with-spread/_config.js
new file mode 100644
index 0000000000..c81f1a9f82
--- /dev/null
+++ b/test/css/samples/siblings-combinator-with-spread/_config.js
@@ -0,0 +1,3 @@
+export default {
+ warnings: []
+};
diff --git a/test/css/samples/siblings-combinator-with-spread/expected.css b/test/css/samples/siblings-combinator-with-spread/expected.css
new file mode 100644
index 0000000000..aacf6e7db5
--- /dev/null
+++ b/test/css/samples/siblings-combinator-with-spread/expected.css
@@ -0,0 +1 @@
+input.svelte-xyz:focus+div.svelte-xyz{color:red}input.svelte-xyz:focus~div.svelte-xyz{color:red}
\ No newline at end of file
diff --git a/test/css/samples/siblings-combinator-with-spread/expected.html b/test/css/samples/siblings-combinator-with-spread/expected.html
new file mode 100644
index 0000000000..d732739701
--- /dev/null
+++ b/test/css/samples/siblings-combinator-with-spread/expected.html
@@ -0,0 +1,2 @@
+
+
Should be red, when input is focused
\ No newline at end of file
diff --git a/test/css/samples/siblings-combinator-with-spread/input.svelte b/test/css/samples/siblings-combinator-with-spread/input.svelte
new file mode 100644
index 0000000000..45c889c1ae
--- /dev/null
+++ b/test/css/samples/siblings-combinator-with-spread/input.svelte
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test/css/samples/siblings-combinator/input.svelte b/test/css/samples/siblings-combinator/input.svelte
new file mode 100644
index 0000000000..3e22076d52
--- /dev/null
+++ b/test/css/samples/siblings-combinator/input.svelte
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test/js/samples/event-modifiers/expected.js b/test/js/samples/event-modifiers/expected.js
index 6aa3c161f9..3901753661 100644
--- a/test/js/samples/event-modifiers/expected.js
+++ b/test/js/samples/event-modifiers/expected.js
@@ -16,41 +16,49 @@ import {
} from "svelte/internal";
function create_fragment(ctx) {
- let div;
- let button0;
+ let div1;
+ let div0;
let t1;
- let button1;
+ let button0;
let t3;
+ let button1;
+ let t5;
let button2;
let mounted;
let dispose;
return {
c() {
- div = element("div");
+ div1 = element("div");
+ div0 = element("div");
+ div0.textContent = "touch me";
+ t1 = space();
button0 = element("button");
button0.textContent = "click me";
- t1 = space();
+ t3 = space();
button1 = element("button");
button1.textContent = "or me";
- t3 = space();
+ t5 = space();
button2 = element("button");
button2.textContent = "or me!";
},
m(target, anchor) {
- insert(target, div, anchor);
- append(div, button0);
- append(div, t1);
- append(div, button1);
- append(div, t3);
- append(div, button2);
+ insert(target, div1, anchor);
+ append(div1, div0);
+ append(div1, t1);
+ append(div1, button0);
+ append(div1, t3);
+ append(div1, button1);
+ append(div1, t5);
+ append(div1, button2);
if (!mounted) {
dispose = [
+ listen(div0, "touchstart", handleTouchstart, { passive: false }),
listen(button0, "click", stop_propagation(prevent_default(handleClick))),
listen(button1, "click", handleClick, { once: true, capture: true }),
listen(button2, "click", handleClick, true),
- listen(div, "touchstart", handleTouchstart, { passive: true })
+ listen(div1, "touchstart", handleTouchstart, { passive: true })
];
mounted = true;
@@ -60,7 +68,7 @@ function create_fragment(ctx) {
i: noop,
o: noop,
d(detaching) {
- if (detaching) detach(div);
+ if (detaching) detach(div1);
mounted = false;
run_all(dispose);
}
diff --git a/test/js/samples/event-modifiers/input.svelte b/test/js/samples/event-modifiers/input.svelte
index 225134f598..c72d58dabb 100644
--- a/test/js/samples/event-modifiers/input.svelte
+++ b/test/js/samples/event-modifiers/input.svelte
@@ -9,6 +9,7 @@
`);
+ assert.strictEqual(div, target.querySelector("div"));
+
+ // make dirty while maintain the value of `value + anotherValue`
+ // should update the content, but not recreate the elements
+ await component.$set({ value: 4, anotherValue: 3 });
+
+ assert.htmlEqual(target.innerHTML, `
\ No newline at end of file
diff --git a/test/runtime/samples/reactive-assignment-in-complex-declaration-with-store-2/_config.js b/test/runtime/samples/reactive-assignment-in-complex-declaration-with-store-2/_config.js
new file mode 100644
index 0000000000..f737cad2aa
--- /dev/null
+++ b/test/runtime/samples/reactive-assignment-in-complex-declaration-with-store-2/_config.js
@@ -0,0 +1,9 @@
+// destructure to store value
+export default {
+ skip_if_ssr: true, // pending https://github.com/sveltejs/svelte/issues/3582
+ html: `