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 21c3018903..ba000981d5 100644
--- a/packages/svelte/src/compiler/phases/1-parse/read/style.js
+++ b/packages/svelte/src/compiler/phases/1-parse/read/style.js
@@ -189,21 +189,21 @@ function read_selector(parser) {
while (parser.index < parser.template.length) {
const start = parser.index;
- if (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('#')) {
+ 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('#')) {
children.push({
type: 'IdSelector',
name: read_identifier(parser),
@@ -392,9 +392,9 @@ function read_declaration(parser) {
* @returns {import('#compiler').Css.Declaration | import('#compiler').Css.Rule}
*/
function read_declaration_or_rule(parser) {
- // We will only allow css nesting using & selector
+ // We will only allow css nesting using & selector for now
// 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 prefix
+ // as most browsers as of 17/11/2023 do not support nesting without & selector
if (parser.match('&')) {
return read_rule(parser)
} else {
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 8e453f6859..6866850598 100644
--- a/packages/svelte/src/compiler/phases/2-analyze/css/Selector.js
+++ b/packages/svelte/src/compiler/phases/2-analyze/css/Selector.js
@@ -117,14 +117,13 @@ export default class Selector {
}
}
this.blocks.forEach((block, index) => {
- console.log(block)
if (block.global) {
remove_global_pseudo_class(block.selectors[0]);
}
if (block.should_encapsulate) {
encapsulate_block(
block,
- index === this.blocks.length - 1
+ index === this.blocks.filter(block => block.nested !== true).length - 1
? attr.repeat(amount_class_specificity_to_increase + 1)
: attr
);
@@ -314,10 +313,6 @@ function block_might_apply_to_node(block, node) {
const name = selector.name.replace(regex_backslash_and_following_character, '$1');
- // if(name === "&") {
- // return POSSIBLE_MATCH;
- // }
-
if (selector.type === 'PseudoClassSelector' && (name === 'host' || name === 'root')) {
return NO_MATCH;
}
@@ -806,6 +801,7 @@ class Block {
this.combinator = combinator;
this.host = false;
this.root = false;
+ this.nested = false;
this.selectors = [];
this.start = -1;
this.end = -1;
@@ -845,6 +841,7 @@ function group_selectors(selector) {
block = new Block(child);
blocks.push(block);
} else {
+ if (child.type === "NestedSelector") block.nested = true;
block.add(child);
}
});
diff --git a/packages/svelte/src/compiler/phases/2-analyze/css/Stylesheet.js b/packages/svelte/src/compiler/phases/2-analyze/css/Stylesheet.js
index eecab0c16e..e2074ddbfb 100644
--- a/packages/svelte/src/compiler/phases/2-analyze/css/Stylesheet.js
+++ b/packages/svelte/src/compiler/phases/2-analyze/css/Stylesheet.js
@@ -6,6 +6,7 @@ import hash from '../utils/hash.js';
// import { extract_ignores_above_position } from '../utils/extract_svelte_ignore.js';
import { push_array } from '../utils/push_array.js';
import { create_attribute } from '../../nodes.js';
+import assert from 'assert';
const regex_css_browser_prefix = /^-((webkit)|(moz)|(o)|(ms))-/;
@@ -61,12 +62,17 @@ class Rule {
/** @type {Atrule | Rule | undefined} */
parent;
+ /** @type {Rule[]} */
+ nested_rules;
+
/**
* @param {import('#compiler').Css.Rule} node
- * @param {any} stylesheet
+ * @param {Stylesheet} stylesheet
* @param {Atrule | Rule | undefined} parent
*/
constructor(node, stylesheet, parent) {
+ // console.log('...............')
+ // console.log(JSON.stringify(node, null, 4))
this.node = node;
this.parent = parent;
this.selectors = node.prelude.children.map((node) => new Selector(node, stylesheet));
@@ -82,9 +88,13 @@ class Rule {
/** @param {import('#compiler').RegularElement | import('#compiler').SvelteElement} node */
apply(node) {
this.selectors.forEach((selector) => selector.apply(node)); // TODO move the logic in here?
+ this.nested_rules.forEach((rule) => rule.apply(node));
}
- /** @param {boolean} dev */
+ /**
+ * @param {boolean} dev
+ * @returns {boolean}
+ */
is_used(dev) {
if (this.parent && this.parent.node.type === 'Atrule' && is_keyframes_node(this.parent.node))
return true;
@@ -93,7 +103,7 @@ class Rule {
// see them in devtools
if (this.declarations.length === 0) return dev;
- return this.selectors.some((s) => s.used);
+ return [this.selectors.some((s) => s.used), this.nested_rules.some(r => r.is_used(dev))].some(Boolean);
}
/**
@@ -120,6 +130,9 @@ class Rule {
this.selectors.forEach((selector) => {
selector.validate(analysis);
});
+ this.nested_rules.forEach((rule) => {
+ rule.validate(analysis);
+ });
}
/** @param {(selector: import('./Selector.js').default) => void} handler */
@@ -127,6 +140,9 @@ class Rule {
this.selectors.forEach((selector) => {
if (!selector.used) handler(selector);
});
+ this.nested_rules.forEach((rule) => {
+ rule.warn_on_unused_selector(handler);
+ });
}
/** @returns number */
@@ -134,6 +150,7 @@ class Rule {
return Math.max(
...this.selectors.map((selector) => selector.get_amount_class_specificity_increased())
);
+ // do we need to check nested rules?
}
/**
@@ -147,7 +164,7 @@ class Rule {
// keep empty rules in dev, because it's convenient to
// see them in devtools
- if (this.declarations.length === 0) {
+ if (this.declarations.length === 0 && this.nested_rules.length === 0) {
if (!dev) {
code.prependRight(this.node.start, '/* (empty) ');
code.appendLeft(this.node.end, '*/');
@@ -198,6 +215,10 @@ class Rule {
code.appendLeft(last, '*/');
}
}
+
+ this.nested_rules.forEach((rule) => {
+ rule.prune(code, dev);
+ });
}
}
@@ -390,9 +411,12 @@ export default class Stylesheet {
const state = {
/** @type {Atrule | undefined} */
- atrule: undefined
+ atrule: undefined,
};
+ /** @type {import('#compiler').Css.Node}*/
+ let prev_node;
+
walk(/** @type {import('#compiler').Css.Node} */ (ast), state, {
Atrule: (node, context) => {
const atrule = new Atrule(node);
@@ -429,12 +453,17 @@ export default class Stylesheet {
},
Rule: (node, context) => {
const rule = new Rule(node, this, context.state.atrule);
+
if (context.state.atrule) {
context.state.atrule.children.push(rule);
} else {
this.children.push(rule);
}
+ if (rule.nested_rules.length > 0) {
+ // Skip nested rules as they are instantiated in the Rule constructor
+ return node
+ }
context.next();
}
});
diff --git a/packages/svelte/tests/css/samples/nested-css/expected.css b/packages/svelte/tests/css/samples/nested-css/expected.css
index 077d0196c2..5bd8968670 100644
--- a/packages/svelte/tests/css/samples/nested-css/expected.css
+++ b/packages/svelte/tests/css/samples/nested-css/expected.css
@@ -10,5 +10,11 @@ button.svelte-xyz {
color: green;
}
+ & .bar.svelte-xyz {
+ & .hello.svelte-xyz {
+ color: orange;
+ }
+ }
+
color: black;
}
\ No newline at end of file
diff --git a/packages/svelte/tests/css/samples/nested-css/input.svelte b/packages/svelte/tests/css/samples/nested-css/input.svelte
index 1e5399ecd7..8e4d3bead9 100644
--- a/packages/svelte/tests/css/samples/nested-css/input.svelte
+++ b/packages/svelte/tests/css/samples/nested-css/input.svelte
@@ -1,6 +1,8 @@
\ No newline at end of file