From 0d70faefd5eb59e9675caf90be244c3c0605db81 Mon Sep 17 00:00:00 2001 From: Tan Li Hau Date: Wed, 3 Feb 2021 23:12:58 +0800 Subject: [PATCH 1/2] fix :host and :global css scoping --- src/compiler/compile/css/Selector.ts | 32 +++++++++++- test/css/samples/host/_config.js | 27 ++++++++++ test/css/samples/host/expected.css | 1 + test/css/samples/host/input.svelte | 27 ++++++++++ .../siblings-combinator-global/_config.js | 50 +++++++++++++++++++ .../siblings-combinator-global/expected.css | 1 + .../siblings-combinator-global/input.svelte | 21 ++++++++ 7 files changed, 157 insertions(+), 2 deletions(-) create mode 100644 test/css/samples/host/_config.js create mode 100644 test/css/samples/host/expected.css create mode 100644 test/css/samples/host/input.svelte create mode 100644 test/css/samples/siblings-combinator-global/_config.js create mode 100644 test/css/samples/siblings-combinator-global/expected.css create mode 100644 test/css/samples/siblings-combinator-global/input.svelte diff --git a/src/compiler/compile/css/Selector.ts b/src/compiler/compile/css/Selector.ts index e910f4333c2d..3c9f453c8d6d 100644 --- a/src/compiler/compile/css/Selector.ts +++ b/src/compiler/compile/css/Selector.ts @@ -84,7 +84,7 @@ export default class Selector { while (i--) { const selector = block.selectors[i]; if (selector.type === 'PseudoElementSelector' || selector.type === 'PseudoClassSelector') { - if (selector.name !== 'root') { + if (selector.name !== 'root' && selector.name !== 'host') { if (i === 0) code.prependRight(selector.start, attr); } continue; @@ -162,7 +162,10 @@ function apply_selector(blocks: Block[], node: Element, to_encapsulate: any[]): if (!block) return false; if (!node) { - return block.global && blocks.every(block => block.global); + return ( + (block.global && blocks.every(block => block.global)) || + (block.host && blocks.length === 0) + ); } switch (block_might_apply_to_node(block, node)) { @@ -182,6 +185,11 @@ function apply_selector(blocks: Block[], node: Element, to_encapsulate: any[]): continue; } + if (ancestor_block.host) { + to_encapsulate.push({ node, block }); + return true; + } + let parent = node; while (parent = get_element_parent(parent)) { if (block_might_apply_to_node(ancestor_block, parent) !== BlockAppliesToNode.NotPossible) { @@ -211,6 +219,19 @@ function apply_selector(blocks: Block[], node: Element, to_encapsulate: any[]): } else if (block.combinator.name === '+' || block.combinator.name === '~') { const siblings = get_possible_element_siblings(node, block.combinator.name === '+'); let has_match = false; + + // NOTE: if we have :global(), we couldn't figure out what is selected within `:global` due to the + // css-tree limitation that does not parse the inner selector of :global + // so unless we are sure there will be no sibling to match, we will consider it as matched + const has_global = blocks.some(block => block.global); + if (has_global) { + if (siblings.size === 0 && get_element_parent(node) !== null) { + return false; + } + to_encapsulate.push({ node, block }); + return true; + } + for (const possible_sibling of siblings.keys()) { if (apply_selector(blocks.slice(), possible_sibling, to_encapsulate)) { to_encapsulate.push({ node, block }); @@ -236,6 +257,10 @@ function block_might_apply_to_node(block: Block, node: Element): BlockAppliesToN const selector = block.selectors[i]; const name = typeof selector.name === 'string' && selector.name.replace(/\\(.)/g, '$1'); + if (selector.type === 'PseudoClassSelector' && name === 'host') { + return BlockAppliesToNode.NotPossible; + } + if (selector.type === 'PseudoClassSelector' || selector.type === 'PseudoElementSelector') { continue; } @@ -541,6 +566,7 @@ function loop_child(children: INode[], adjacent_only: boolean) { class Block { global: boolean; + host: boolean; combinator: CssNode; selectors: CssNode[] start: number; @@ -550,6 +576,7 @@ class Block { constructor(combinator: CssNode) { this.combinator = combinator; this.global = false; + this.host = false; this.selectors = []; this.start = null; @@ -562,6 +589,7 @@ class Block { if (this.selectors.length === 0) { this.start = selector.start; this.global = selector.type === 'PseudoClassSelector' && selector.name === 'global'; + this.host = selector.type === 'PseudoClassSelector' && selector.name === 'host'; } this.selectors.push(selector); diff --git a/test/css/samples/host/_config.js b/test/css/samples/host/_config.js new file mode 100644 index 000000000000..61539db5bce6 --- /dev/null +++ b/test/css/samples/host/_config.js @@ -0,0 +1,27 @@ +export default { + warnings: [ + { + code: 'css-unused-selector', + message: 'Unused CSS selector ":host > span"', + pos: 147, + start: { + character: 147, + column: 1, + line: 18 + }, + end: { + character: 159, + column: 13, + line: 18 + }, + frame: ` + 16: } + 17: + 18: :host > span { + ^ + 19: color: red; + 20: } + ` + } + ] +}; diff --git a/test/css/samples/host/expected.css b/test/css/samples/host/expected.css new file mode 100644 index 000000000000..28683691c0bc --- /dev/null +++ b/test/css/samples/host/expected.css @@ -0,0 +1 @@ +:host h1.svelte-xyz{color:red}:host>h1.svelte-xyz{color:red}:host>.svelte-xyz{color:red}:host span.svelte-xyz{color:red} \ No newline at end of file diff --git a/test/css/samples/host/input.svelte b/test/css/samples/host/input.svelte new file mode 100644 index 000000000000..4c1efcd98348 --- /dev/null +++ b/test/css/samples/host/input.svelte @@ -0,0 +1,27 @@ + + +

Hello!

+ +
+ World! +
diff --git a/test/css/samples/siblings-combinator-global/_config.js b/test/css/samples/siblings-combinator-global/_config.js new file mode 100644 index 000000000000..342c09ce48ab --- /dev/null +++ b/test/css/samples/siblings-combinator-global/_config.js @@ -0,0 +1,50 @@ +export default { + warnings: [ + { + code: 'css-unused-selector', + message: 'Unused CSS selector ":global(input) + span"', + pos: 239, + start: { + character: 239, + column: 2, + line: 9 + }, + end: { + character: 260, + column: 23, + line: 9 + }, + frame: ` + 7: :global(input) ~ p { color: red; } + 8: + 9: :global(input) + span { color: red; } + ^ + 10: :global(input) ~ span { color: red; } + 11: + ` + }, + { + code: 'css-unused-selector', + message: 'Unused CSS selector ":global(input) ~ span"', + pos: 279, + start: { + character: 279, + column: 2, + line: 10 + }, + end: { + character: 300, + column: 23, + line: 10 + }, + frame: ` + 8: + 9: :global(input) + span { color: red; } + 10: :global(input) ~ span { color: red; } + ^ + 11: + 12: + ` + } + ] +}; diff --git a/test/css/samples/siblings-combinator-global/expected.css b/test/css/samples/siblings-combinator-global/expected.css new file mode 100644 index 000000000000..83a4713156fb --- /dev/null +++ b/test/css/samples/siblings-combinator-global/expected.css @@ -0,0 +1 @@ +input+div.svelte-xyz{color:red}input~div.svelte-xyz{color:red}input+h1.svelte-xyz{color:red}input~h1.svelte-xyz{color:red}input+p.svelte-xyz{color:red}input~p.svelte-xyz{color:red} \ No newline at end of file diff --git a/test/css/samples/siblings-combinator-global/input.svelte b/test/css/samples/siblings-combinator-global/input.svelte new file mode 100644 index 000000000000..ec06f3c0156c --- /dev/null +++ b/test/css/samples/siblings-combinator-global/input.svelte @@ -0,0 +1,21 @@ + + +

Hello!

+ +
+ World! +
+ +{#each [] as _} +

+{/each} \ No newline at end of file From 10642d0f02a8b1b761a1398c9c8a673bfbc51b54 Mon Sep 17 00:00:00 2001 From: Conduitry Date: Mon, 8 Feb 2021 11:16:36 -0500 Subject: [PATCH 2/2] update changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e24c11d4252..198681f3680c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Svelte changelog +## Unreleased + +* Fix scoping of selectors with `:global()` and `~` sibling combinators ([#5499](https://github.com/sveltejs/svelte/issues/5499)) +* Fix removal of `:host` selectors as unused when compiling to a custom element ([#5946](https://github.com/sveltejs/svelte/issues/5946)) + ## 3.32.1 * Warn when using `module` variables reactively, and close weird reactivity loophole ([#5847](https://github.com/sveltejs/svelte/pull/5847))