From d9174c4fbf8ea4f715b490ad9b4bdb96c03502e2 Mon Sep 17 00:00:00 2001 From: Albert Marashi Date: Mon, 20 Nov 2023 15:41:01 +1030 Subject: [PATCH] Handle rule nesting, remove NestedSelector from SimpleSelector, improve logic for encapsulation --- .../src/compiler/phases/1-parse/read/style.js | 47 ++++++++++--------- .../compiler/phases/2-analyze/css/Selector.js | 21 +++++---- packages/svelte/src/compiler/types/css.d.ts | 13 +++-- 3 files changed, 43 insertions(+), 38 deletions(-) diff --git a/packages/svelte/src/compiler/phases/1-parse/read/style.js b/packages/svelte/src/compiler/phases/1-parse/read/style.js index ba000981d5..cf49b5d8da 100644 --- a/packages/svelte/src/compiler/phases/1-parse/read/style.js +++ b/packages/svelte/src/compiler/phases/1-parse/read/style.js @@ -129,14 +129,15 @@ function read_at_rule(parser) { /** * @param {import('../index.js').Parser} parser + * @param {boolean} nested Whether this rule is nested inside another rule * @returns {import('#compiler').Css.Rule} */ -function read_rule(parser) { +function read_rule(parser, nested = false) { const start = parser.index; return { type: 'Rule', - prelude: read_selector_list(parser), + prelude: read_selector_list(parser, nested), block: read_block(parser), start, end: parser.index @@ -145,16 +146,17 @@ function read_rule(parser) { /** * @param {import('../index.js').Parser} parser + * @param {boolean} nested Whether this selector list is nested inside another rule * @returns {import('#compiler').Css.SelectorList} */ -function read_selector_list(parser) { +function read_selector_list(parser, nested) { /** @type {import('#compiler').Css.Selector[]} */ const children = []; const start = parser.index; while (parser.index < parser.template.length) { - children.push(read_selector(parser)); + children.push(read_selector(parser, nested)); const end = parser.index; @@ -178,32 +180,33 @@ function read_selector_list(parser) { /** * @param {import('../index.js').Parser} parser + * @param {boolean} nested Whether this selector is nested inside another rule * @returns {import('#compiler').Css.Selector} */ -function read_selector(parser) { +function read_selector(parser, nested) { const list_start = parser.index; - /** @type {Array} */ + /** @type {Array} */ const children = []; while (parser.index < parser.template.length) { const start = parser.index; - if (parser.eat('*')) { - children.push({ - type: 'TypeSelector', - name: '*', - start, - end: parser.index - }); - } else if (parser.eat('&')){ - children.push({ - type: 'NestedSelector', - name: '&', - start, - end: parser.index - }); - } else if (parser.eat('#')) { + if (nested && parser.eat('&')){ + children.push({ + type: 'NestedSelector', + name: '&', + start, + end: parser.index + }); + } else if (parser.eat('*')) { + children.push({ + type: 'TypeSelector', + name: '*', + start, + end: parser.index + }); + } else if (parser.eat('#')) { children.push({ type: 'IdSelector', name: read_identifier(parser), @@ -396,7 +399,7 @@ function read_declaration_or_rule(parser) { // due to complexities with https://bugs.chromium.org/p/chromium/issues/detail?id=1427259 // as most browsers as of 17/11/2023 do not support nesting without & selector if (parser.match('&')) { - return read_rule(parser) + return read_rule(parser, true) } else { return read_declaration(parser); } diff --git a/packages/svelte/src/compiler/phases/2-analyze/css/Selector.js b/packages/svelte/src/compiler/phases/2-analyze/css/Selector.js index 6866850598..8ca6869765 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/css/Selector.js +++ b/packages/svelte/src/compiler/phases/2-analyze/css/Selector.js @@ -100,15 +100,12 @@ export default class Selector { while (i--) { const selector = block.selectors[i]; if (selector.type === 'PseudoElementSelector' || selector.type === 'PseudoClassSelector') { - if (selector.name !== 'root' && selector.name !== 'host') { + if (!block.root && !block.host && !block.nested) { if (i === 0) code.prependRight(selector.start, attr); } continue; } - if (selector.type === "NestedSelector") { - // do we want to add the attr to the nested selector? - // it's kind of implied that it's a child of the parent selector - } else if (selector.type === 'TypeSelector' && selector.name === '*') { + if (selector.type === 'TypeSelector' && selector.name === '*') { code.update(selector.start, selector.end, attr); } else { code.appendLeft(selector.end, attr); @@ -123,7 +120,7 @@ export default class Selector { if (block.should_encapsulate) { encapsulate_block( block, - index === this.blocks.filter(block => block.nested !== true).length - 1 + index === this.blocks.length - 1 + (block.nested ? 1 : 0) ? attr.repeat(amount_class_specificity_to_increase + 1) : attr ); @@ -838,12 +835,18 @@ function group_selectors(selector) { selector.children.forEach((child) => { if (child.type === 'Combinator') { - block = new Block(child); - blocks.push(block); + if(block.nested && !block.combinator) { + block.combinator = child; + } else { + block = new Block(child); + blocks.push(block); + } + } else if (child.type === "NestedSelector") { + block.nested = true; } else { - if (child.type === "NestedSelector") block.nested = true; block.add(child); } }); + return blocks; } diff --git a/packages/svelte/src/compiler/types/css.d.ts b/packages/svelte/src/compiler/types/css.d.ts index c1adee00b9..cfbfd97381 100644 --- a/packages/svelte/src/compiler/types/css.d.ts +++ b/packages/svelte/src/compiler/types/css.d.ts @@ -25,7 +25,7 @@ export interface SelectorList extends BaseNode { export interface Selector extends BaseNode { type: 'Selector'; - children: Array; + children: Array; } export interface TypeSelector extends BaseNode { @@ -67,11 +67,6 @@ export interface Percentage extends BaseNode { value: string; } -export interface NestedSelector extends BaseNode { - type: 'NestedSelector'; - name: "&"; -} - export type SimpleSelector = | TypeSelector | IdSelector @@ -79,7 +74,6 @@ export type SimpleSelector = | AttributeSelector | PseudoElementSelector | PseudoClassSelector - | NestedSelector | Percentage; export interface Combinator extends BaseNode { @@ -87,6 +81,11 @@ export interface Combinator extends BaseNode { name: string; } +export interface NestingSelector extends BaseNode { + type: 'NestedSelector'; + name: "&"; +} + export interface Block extends BaseNode { type: 'Block'; children: Array;